|
1 | 1 | # pointer-events
|
2 | 2 |
|
3 |
| -*framework agnostic pointer-events implementation for three.js* |
| 3 | +_framework agnostic pointer-events implementation for three.js_ |
4 | 4 |
|
5 | 5 | based on [🎯 Designing Pointer-events for 3D & XR](https://polar.sh/bbohlender/posts/designing-pointer-events-for-3d)
|
6 | 6 |
|
7 | 7 | ## How to use
|
8 | 8 |
|
9 | 9 | ```js
|
10 |
| -import * as THREE from 'three'; |
11 |
| -import { forwardHtmlEvents } from '@pmndrs/pointer-events'; |
| 10 | +import * as THREE from 'three' |
| 11 | +import { forwardHtmlEvents } from '@pmndrs/pointer-events' |
12 | 12 |
|
13 |
| -const canvas = document.getElementById("canvas") |
14 |
| -const scene = new THREE.Scene(); |
15 |
| -const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 ); |
16 |
| -camera.position.z = 1; |
| 13 | +const canvas = document.getElementById('canvas') |
| 14 | +const scene = new THREE.Scene() |
| 15 | +const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10) |
| 16 | +camera.position.z = 1 |
17 | 17 | forwardHtmlEvents(canvas, camera, scene)
|
18 | 18 |
|
19 |
| -const width = window.innerWidth, height = window.innerHeight; |
| 19 | +const width = window.innerWidth, |
| 20 | + height = window.innerHeight |
20 | 21 |
|
21 |
| -const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 ); |
22 |
| -const material = new THREE.MeshBasicMaterial({ color: new THREE.Color("red") }); |
23 |
| -const mesh = new THREE.Mesh( geometry, material ); |
24 |
| -scene.add( mesh ); |
| 22 | +const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2) |
| 23 | +const material = new THREE.MeshBasicMaterial({ color: new THREE.Color('red') }) |
| 24 | +const mesh = new THREE.Mesh(geometry, material) |
| 25 | +scene.add(mesh) |
25 | 26 |
|
26 |
| -mesh.addEventListener("pointerover", () => material.color.set("blue")) |
27 |
| -mesh.addEventListener("pointerout", () => material.color.set("red")) |
| 27 | +mesh.addEventListener('pointerover', () => material.color.set('blue')) |
| 28 | +mesh.addEventListener('pointerout', () => material.color.set('red')) |
28 | 29 |
|
29 |
| -const renderer = new THREE.WebGLRenderer( { antialias: true } ); |
30 |
| -renderer.setSize( width, height ); |
31 |
| -renderer.setAnimationLoop(() => renderer.render( scene, camera )); |
| 30 | +const renderer = new THREE.WebGLRenderer({ antialias: true }) |
| 31 | +renderer.setSize(width, height) |
| 32 | +renderer.setAnimationLoop(() => renderer.render(scene, camera)) |
32 | 33 | ```
|
33 | 34 |
|
34 | 35 | ## Filtering
|
35 | 36 |
|
36 | 37 | Based on the css `pointer-events` property, the behavior of pointer events can be configured with the values `none`, `listener`, or `auto`.
|
37 | 38 |
|
38 | 39 | ```js
|
39 |
| -object.pointerEvents = "none"; |
| 40 | +object.pointerEvents = 'none' |
40 | 41 | ```
|
41 | 42 |
|
42 |
| -The values `none` and `auto` correspond to the css properties, where `none` means that an object is not directly targetted and `auto` means the object is always targetted for events. The additional value `listener`, which is the default value, expresses that the object is only targetted by events if the object has any listeners. In 3D scenes this default is more reasonable than `auto`, which is the default in the web, because 3D scenes often contain semi-transparent content, such as particles, that should not catch pointer events by default. |
| 43 | +The values `none` and `auto` correspond to the css properties, where `none` means that an object is not directly targetted and `auto` means the object is always targetted for events. The additional value `listener`, which is the default value, expresses that the object is only targetted by events if the object has any listeners. In 3D scenes this default is more reasonable than `auto`, which is the default in the web, because 3D scenes often contain semi-transparent content, such as particles, that should not catch pointer events by default. |
43 | 44 |
|
44 | 45 | In addition to the `pointerEvents` property, each 3D object can also filter events based on the `pointerType` with the `pointerEventsType` property. This property defaults to the value `all`, which expresses that pointer events from pointers of all types should be accepted. To filter specific pointer types, such as `screen-mouse`, which represents a normal mouse used through a 2D screen, `pointerEventsType` can be set to `{ allow: "screen-mouse" }` or `{ deny: "screen-touch" }`. `pointerEventsType`'s `allow` and `deny` accept strings and array of strings. In case more custom logic is needed, `pointerEventsType` also accepts a function. In general the pointer types `screen-touch`, `screen-pen`, `ray`, `grab`, and `touch` are used by default. For pointer events that were forwarded through a portal using `forwardObjectEvents`, their `pointerType` is prefixed with `forward-`, while events forwarded from the dom to the scene are prefixed with `screen-`.
|
45 | 46 |
|
46 | 47 | ## But wait ... there's more
|
47 | 48 |
|
48 |
| -Create your own `Pointer` that can represent a WebXR controller or something else. These `Pointer` can use a normal `Ray` for intersection, or a set of `Lines`, or even a `Sphere`, for grab and touch events. |
| 49 | +Create your own `Pointer` that can represent a WebXR controller or something else. These `Pointer` can use a normal `Ray` for intersection, or a set of `Lines`, or even a `Sphere`, for grab and touch events. |
| 50 | + |
| 51 | +## Performance |
| 52 | + |
| 53 | +In some cases multi-modal interactivity requires multiple pointers at the same time. Executing `pointer.move`, such as in the following example, can lead to performance issues because the scene graph will be traversed several times. |
| 54 | + |
| 55 | +```ts |
| 56 | +leftGrabPointer.move() |
| 57 | +leftTouchPointer.move() |
| 58 | +leftRayPointer.move() |
| 59 | +rightGrabPointer.move() |
| 60 | +rightTouchPointer.move() |
| 61 | +rightRayPointer.move() |
| 62 | +``` |
| 63 | + |
| 64 | +In this case, performance can be improved by combining the pointer using `CombinedPointer`, which will traverse the scene graph once per combined pointer, calculating the intersections for each pointer on each object. |
| 65 | + |
| 66 | +```ts |
| 67 | +const leftPointer = new CombinedPointer() |
| 68 | +const rightPointer = new CombinedPointer() |
| 69 | +leftPointer.register(leftGrabPointer) |
| 70 | +leftPointer.register(leftTouchPointer) |
| 71 | +leftPointer.register(leftRayPointer) |
| 72 | +rightPointer.register(rightGrabPointer) |
| 73 | +rightPointer.register(rightTouchPointer) |
| 74 | +rightPointer.register(rightRayPointer) |
| 75 | + |
| 76 | +leftPointer.move() |
| 77 | +rightPointer.move() |
| 78 | +``` |
| 79 | + |
| 80 | + |
0 commit comments