PointerLockControls

Video Lecture

PointerLock Controls PointerLock Controls

Description

The PointerLockControls implements the inbuilt browsers Pointer Lock API. It provides input methods based on the movement of the mouse over time (i.e., deltas), not just the absolute position of the mouse cursor in the viewport. It gives you access to raw mouse movement, locks the target of mouse events to a single element, eliminates limits on how far mouse movement can go in a single direction, and removes the cursor from view. It is ideal for first person 3D games, for example.

Start Code

./src/server/server.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import express from "express"
import path from "path"
import http from "http"

const port: number = 3000

class App {
    private server: http.Server
    private port: number

    constructor(port: number) {
        this.port = port
        const app = express()
        app.use(express.static(path.join(__dirname, '../client')))
        app.use('/build/three.module.js', express.static(path.join(__dirname, '../../node_modules/three/build/three.module.js')))
        app.use('/jsm/controls/PointerLockControls', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/controls/PointerLockControls.js')))
        app.use('/jsm/libs/stats.module', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/libs/stats.module.js')))
        this.server = new http.Server(app);
    }

    public Start() {
        this.server.listen(this.port, () => {
            console.log( `Server listening on port ${this.port}.` )
        })
    }
}

new App(port).Start()

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
import * as THREE from '/build/three.module.js'
import { PointerLockControls } from '/jsm/controls/PointerLockControls'
import Stats from '/jsm/libs/stats.module'

const scene: THREE.Scene = new THREE.Scene()

var light = new THREE.AmbientLight();
scene.add(light);

const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)

const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const menuPanel = document.getElementById('menuPanel');
const startButton = document.getElementById('startButton');
// startButton.addEventListener('click', function () {
//     controls.lock();
// }, false);

const controls = new PointerLockControls(camera, renderer.domElement)
//controls.addEventListener('change', () => console.log("Controls Change"))
// controls.addEventListener('lock', () => menuPanel.style.display = 'none');
// controls.addEventListener('unlock', () => menuPanel.style.display = 'block');



const planeGeometry: THREE.PlaneGeometry = new THREE.PlaneGeometry(100, 100, 50, 50)
const material: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true })
const plane: THREE.Mesh = new THREE.Mesh(planeGeometry, material)
plane.rotateX(-Math.PI / 2)
scene.add(plane)

let cubes: THREE.Mesh[] = new Array()
for (let i = 0; i < 100; i++) {
    const geo: THREE.BoxGeometry = new THREE.BoxGeometry(Math.random() * 4, Math.random() * 16, Math.random() * 4)
    const mat: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({ wireframe: true })
    switch (i % 3) {
        case 0:
            mat.color = new THREE.Color(0xff0000)
            break;
        case 1:
            mat.color = new THREE.Color(0xffff00)
            break;
        case 2:
            mat.color = new THREE.Color(0x0000ff)
            break;
    }
    const cube = new THREE.Mesh(geo, mat)
    cubes.push(cube)
}
cubes.forEach((c) => {
    c.position.x = (Math.random() * 100) - 50
    c.position.z = (Math.random() * 100) - 50
    c.geometry.computeBoundingBox()
    c.position.y = ((c.geometry.boundingBox as THREE.Box3).max.y - (c.geometry.boundingBox as THREE.Box3).min.y) / 2
    scene.add(c)
});

camera.position.y = 1
camera.position.z = 2

// const onKeyDown = function (event) {
//     switch (event.keyCode) {
//         case 87: // w
//             controls.moveForward(.25)
//             break;
//         case 65: // a
//             controls.moveRight(-.25)
//             break;
//         case 83: // s
//             controls.moveForward(-.25)
//             break;
//         case 68: // d
//             controls.moveRight(.25)
//             break;
//     }
// };
// document.addEventListener('keydown', onKeyDown, false);

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render()
}

const stats = Stats()
document.body.appendChild(stats.dom)

var animate = function () {
    requestAnimationFrame(animate)

    //controls.update()

    render()

    stats.update()
};

function render() {
    renderer.render(scene, camera)
}
animate();

./dist/client/index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>

<head>
    <title>Three.js TypeScript Tutorials by Sean Bradley</title>
    <style>
        body {
            overflow: hidden;
            margin: 0px;
        }

        #menuPanel {
            position: absolute;
            background-color: rgba(255, 255, 255, 0.5);
            top: 0px;
            left: 0px;
            width: 100%;
            height: 100%;
        }

        #startButton {
            height: 50px;
            width: 200px;
            margin: -25px -100px;
            position: relative;
            top: 50%;
            left: 50%;
            font-size: 32px;
        }
    </style>
</head>

<body>
    <div id="menuPanel">
        <button id="startButton">Click to Start</button>
    </div>
    <script type="module" src="client.js"></script>
</body>

</html>

Final Scripts

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
import * as THREE from '/build/three.module.js'
import { PointerLockControls } from '/jsm/controls/PointerLockControls'
import Stats from '/jsm/libs/stats.module'

const scene: THREE.Scene = new THREE.Scene()

var light = new THREE.AmbientLight();
scene.add(light);

const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)

const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const menuPanel = document.getElementById('menuPanel');
const startButton = document.getElementById('startButton');
startButton.addEventListener('click', function () {
    controls.lock();
}, false);

const controls = new PointerLockControls(camera, renderer.domElement)
//controls.addEventListener('change', () => console.log("Controls Change"))
controls.addEventListener('lock', () => menuPanel.style.display = 'none');
controls.addEventListener('unlock', () => menuPanel.style.display = 'block');

const planeGeometry: THREE.PlaneGeometry = new THREE.PlaneGeometry(100, 100, 50, 50)
const material: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true })
const plane: THREE.Mesh = new THREE.Mesh(planeGeometry, material)
plane.rotateX(-Math.PI / 2)
scene.add(plane)

let cubes: THREE.Mesh[] = new Array()
for (let i = 0; i < 100; i++) {
    const geo: THREE.BoxGeometry = new THREE.BoxGeometry(Math.random() * 4, Math.random() * 16, Math.random() * 4)
    const mat: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({ wireframe: true })
    switch (i % 3) {
        case 0:
            mat.color = new THREE.Color(0xff0000)
            break;
        case 1:
            mat.color = new THREE.Color(0xffff00)
            break;
        case 2:
            mat.color = new THREE.Color(0x0000ff)
            break;
    }
    const cube = new THREE.Mesh(geo, mat)
    cubes.push(cube)
}
cubes.forEach((c) => {
    c.position.x = (Math.random() * 100) - 50
    c.position.z = (Math.random() * 100) - 50
    c.geometry.computeBoundingBox()
    c.position.y = ((c.geometry.boundingBox as THREE.Box3).max.y - (c.geometry.boundingBox as THREE.Box3).min.y) / 2
    scene.add(c)
});

camera.position.y = 1
camera.position.z = 2

const onKeyDown = function (event) {
     switch (event.keyCode) {
        case 87: // w
            controls.moveForward(.25)
            break;
        case 65: // a
            controls.moveRight(-.25)
            break;
        case 83: // s
            controls.moveForward(-.25)
            break;
        case 68: // d
            controls.moveRight(.25)
            break;
     }
};
document.addEventListener('keydown', onKeyDown, false);

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render()
}

const stats = Stats()
document.body.appendChild(stats.dom)

var animate = function () {
    requestAnimationFrame(animate)

    //controls.update()

    render()

    stats.update()
};

function render() {
    renderer.render(scene, camera)
}
animate();

Pointer Lock API