Skip to content

Commit e09d1b8

Browse files
committed
chore: add a WebWorker example
1 parent fe99ab4 commit e09d1b8

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed

examples/PseudoElement.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { EventDispatcher } from '../dist/camera-controls.module.js';
2+
3+
/**
4+
* Represents a pseudo element, works in WebWorker.
5+
* @extends EventDispatcher
6+
*/
7+
export class PseudoElement extends EventDispatcher {
8+
9+
/** @type {PseudoDocument} */
10+
ownerDocument = new PseudoDocument();
11+
/** @type {any} */
12+
style = {};
13+
/** @private @type {DOMRect} */
14+
_domRect = new DOMRect();
15+
16+
constructor() {
17+
18+
super();
19+
20+
}
21+
22+
/**
23+
* @returns {DOMRect} The DOMRect.
24+
*/
25+
getBoundingClientRect() {
26+
27+
return DOMRect.fromRect( this._domRect );
28+
29+
}
30+
31+
/**
32+
* Just for compatibility. doesn't work.
33+
* @returns {Promise<void>} A promise that rejects.
34+
*/
35+
requestPointerLock() {
36+
37+
return Promise.reject();
38+
39+
}
40+
41+
/**
42+
* Just for compatibility. do nothing.
43+
* @param {string} _ The attribute name.
44+
* @param {string} __ The attribute value.
45+
*/
46+
setAttribute( _, __ ) {}
47+
48+
/**
49+
* Updates the PseudoElement size based on a given bound.
50+
* @param {DOMRect} bound The new bound.
51+
*/
52+
update( bound ) {
53+
54+
this._domRect.x = bound.x;
55+
this._domRect.y = bound.y;
56+
this._domRect.width = bound.width;
57+
this._domRect.height = bound.height;
58+
59+
}
60+
61+
}
62+
63+
/**
64+
* Represents a pseudo document.
65+
* @extends EventDispatcher
66+
*/
67+
class PseudoDocument extends EventDispatcher {
68+
69+
/**
70+
* Creates an instance of PseudoDocument.
71+
*/
72+
constructor() {
73+
74+
super();
75+
76+
}
77+
78+
/**
79+
* Exits pointer lock.
80+
*/
81+
exitPointerLock() {}
82+
83+
/**
84+
* Just for compatibility. do nothing.
85+
* @returns {PseudoElement|null} The element locking the pointer.
86+
*/
87+
get pointerLockElement() {
88+
89+
return null;
90+
91+
}
92+
93+
}

examples/worker.html

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width">
6+
<title>=^.^=</title>
7+
<link rel="stylesheet" href="./style.css">
8+
</head>
9+
<body>
10+
<div class="info">
11+
<p><a href="https://github.com/yomotsu/camera-controls">GitHub repo</a></p>
12+
<button id="resetButton">reset</button>
13+
</div>
14+
15+
<canvas></canvas>
16+
17+
<script type="module">
18+
const canvas = document.querySelector( 'canvas' );
19+
canvas.width = window.innerWidth;
20+
canvas.height = window.innerHeight;
21+
const canvasBounds = canvas.getBoundingClientRect();
22+
const offscreenCanvas = canvas.transferControlToOffscreen();
23+
24+
const worker = new Worker( './worker.js', { type: 'module' } );
25+
worker.postMessage( {
26+
action: 'init',
27+
payload: {
28+
canvas: offscreenCanvas,
29+
left: canvasBounds.left,
30+
top: canvasBounds.top,
31+
width: canvasBounds.width,
32+
height: canvasBounds.height,
33+
} },
34+
[ offscreenCanvas ]
35+
);
36+
37+
const onPointerDown = ( event ) => {
38+
39+
const pseudoPointerEvent = {
40+
pointerId: event.pointerId,
41+
pointerType: event.pointerType,
42+
clientX: event.clientX,
43+
clientY: event.clientY,
44+
buttons: event.buttons,
45+
};
46+
47+
canvas.ownerDocument.removeEventListener( 'pointermove', onPointerMove, { passive: false } );
48+
canvas.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
49+
50+
canvas.ownerDocument.addEventListener( 'pointermove', onPointerMove, { passive: false } );
51+
canvas.ownerDocument.addEventListener( 'pointerup', onPointerUp );
52+
53+
worker.postMessage( { action: 'pointerdown', payload: { event: pseudoPointerEvent } } );
54+
55+
};
56+
57+
const onPointerMove = ( event ) => {
58+
59+
if ( event.cancelable ) event.preventDefault();
60+
61+
const pseudoPointerEvent = {
62+
pointerId: event.pointerId,
63+
pointerType: event.pointerType,
64+
clientX: event.clientX,
65+
clientY: event.clientY,
66+
movementX: event.movementX,
67+
movementY: event.movementY,
68+
buttons: event.buttons,
69+
}
70+
71+
worker.postMessage( { action: 'pointermove', payload: { event: pseudoPointerEvent } } );
72+
73+
};
74+
75+
const onPointerUp = ( event ) => {
76+
77+
const pseudoPointerEvent = {
78+
pointerId: event.pointerId,
79+
pointerType: event.pointerType,
80+
};
81+
82+
worker.postMessage( { action: 'pointerup', payload: { event: pseudoPointerEvent } } );
83+
84+
}
85+
86+
const endDragging = () => {
87+
88+
canvas.ownerDocument.removeEventListener( 'pointermove', onPointerMove, { passive: false } );
89+
canvas.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
90+
91+
};
92+
93+
worker.addEventListener( 'message', ( { data } ) => data?.action === 'controlend' && endDragging() );
94+
canvas.addEventListener( 'pointerdown', onPointerDown );
95+
96+
97+
resetButton.addEventListener( 'click', () => worker.postMessage( { action: 'reset' } ) );
98+
99+
// debug log
100+
worker.addEventListener( 'message', ( { data } ) => console.log( data ) );
101+
</script>
102+
103+
</body>
104+
</html>

examples/worker.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import * as THREE from 'https://unpkg.com/three@0.161.0/build/three.module.js';
2+
import CameraControls from '../dist/camera-controls.module.js';
3+
import { PseudoElement } from './PseudoElement.js';
4+
5+
CameraControls.install( { THREE } );
6+
7+
// DOM element doesn't exist in WebWorker. use a virtual element in CameraControls instead.
8+
const pseudoElement = new PseudoElement();
9+
let cameraControls;
10+
11+
self.onmessage = ( { data } ) => {
12+
13+
const { action, payload } = data;
14+
15+
switch ( action ) {
16+
17+
case 'init': {
18+
19+
const { canvas, left, top, width, height } = payload;
20+
canvas.style = { width: '', height: '' };
21+
22+
const clock = new THREE.Clock();
23+
const scene = new THREE.Scene();
24+
const camera = new THREE.PerspectiveCamera( 60, width / height, 0.01, 100 );
25+
camera.position.set( 0, 0, 5 );
26+
const renderer = new THREE.WebGLRenderer( { canvas } );
27+
renderer.setSize( width, height );
28+
29+
pseudoElement.update( { left, top, width, height } );
30+
cameraControls = new CameraControls( camera, pseudoElement, self );
31+
cameraControls.addEventListener( 'controlend', () => self.postMessage( { action: 'controlend' } ) );
32+
33+
const mesh = new THREE.Mesh(
34+
new THREE.BoxGeometry( 1, 1, 1 ),
35+
new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } )
36+
);
37+
scene.add( mesh );
38+
39+
const gridHelper = new THREE.GridHelper( 50, 50 );
40+
gridHelper.position.y = - 1;
41+
scene.add( gridHelper );
42+
43+
renderer.render( scene, camera );
44+
45+
( function anim() {
46+
47+
const delta = clock.getDelta();
48+
// const elapsed = clock.getElapsedTime();
49+
const updated = cameraControls.update( delta );
50+
51+
// if ( elapsed > 30 ) return;
52+
53+
requestAnimationFrame( anim );
54+
55+
if ( updated ) {
56+
57+
renderer.render( scene, camera );
58+
self.postMessage( 'rendered' );
59+
60+
}
61+
62+
} )();
63+
64+
break;
65+
66+
}
67+
68+
case 'pointerdown':
69+
case 'pointermove':
70+
case 'pointerup': {
71+
72+
const { event } = payload;
73+
pseudoElement.dispatchEvent( { type: action, ...event } );
74+
break;
75+
76+
}
77+
78+
case 'reset': {
79+
80+
cameraControls.reset( true );
81+
break;
82+
83+
}
84+
85+
}
86+
87+
// debug log
88+
self.postMessage( data.action );
89+
90+
};

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ A camera control for three.js, similar to THREE.OrbitControls yet supports smoot
4646
- [path animation](https://yomotsu.github.io/camera-controls/examples/path-animation.html) (with [gsap](https://www.npmjs.com/package/gsap))
4747
- [complex transitions with `await`](https://yomotsu.github.io/camera-controls/examples/await-transitions.html)
4848
- [set view padding](https://yomotsu.github.io/camera-controls/examples/padding-with-view-offset.html)
49+
- [WebWorker (OffscreenCanvas)](https://yomotsu.github.io/camera-controls/examples/worker.html)
4950
- [outside of iframe dragging](https://yomotsu.github.io/camera-controls/examples/iframe.html)
5051
- [in react-three-fiber (simplest)](https://codesandbox.io/s/react-three-fiber-camera-controls-4jjor?file=/src/App.tsx)
5152
- [in react-three-fiber (drei official)](https://codesandbox.io/s/sew669) (see [doc](https://github.com/pmndrs/drei#cameracontrols))

0 commit comments

Comments
 (0)