├── .env
├── .eslintcache
├── .gitignore
├── README.md
├── package.json
├── public
├── index.html
├── manifest.json
└── robots.txt
├── readmeAssets
├── withOffsetting2.gif
└── withoutOffsetting2.gif
├── src
├── Box.js
├── Reticle.jsx
├── index.js
└── styles.css
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"/Users/jacobjaffe/Projects/pointer-lock-controls-offset-example/src/index.js":"1","/Users/jacobjaffe/Projects/pointer-lock-controls-offset-example/src/Reticle.jsx":"2","/Users/jacobjaffe/Projects/pointer-lock-controls-offset-example/src/Box.js":"3"},{"size":2823,"mtime":1607021329721,"results":"4","hashOfConfig":"5"},{"size":480,"mtime":1607020648574,"results":"6","hashOfConfig":"5"},{"size":960,"mtime":1607018069306,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"1okbr6x",{"filePath":"10","messages":"11","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"12","messages":"13","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/jacobjaffe/Projects/pointer-lock-controls-offset-example/src/index.js",["14","15","16"],"/Users/jacobjaffe/Projects/pointer-lock-controls-offset-example/src/Reticle.jsx",[],"/Users/jacobjaffe/Projects/pointer-lock-controls-offset-example/src/Box.js",[],{"ruleId":"17","severity":1,"message":"18","line":1,"column":36,"nodeType":"19","messageId":"20","endLine":1,"endColumn":44},{"ruleId":"17","severity":1,"message":"21","line":59,"column":9,"nodeType":"19","messageId":"20","endLine":59,"endColumn":21},{"ruleId":"17","severity":1,"message":"22","line":60,"column":19,"nodeType":"19","messageId":"20","endLine":60,"endColumn":24},"no-unused-vars","'useState' is defined but never used.","Identifier","unusedVar","'mouseReticle' is assigned a value but never used.","'mouse' is assigned a value but never used."]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pointer Lock Controls Offset Example
2 |
3 | This repo provides an example of how to use mouse offsets with pointer lock controls with Three Fiber.
4 |
5 | Normally, when raycasting for pointer events, the raycast occurs from the offset of the mouse and camera.
6 |
7 | However, when using a PointerLockControl, the offset should be disregarded, as the mouse position does _not_ change. This can cause issues unless the mouse is set to the middle of the canvas before entering the pointer lock.
8 |
9 | By setting the offset to be the middle of the canvas, the offset is ignored, and the raycasts operate as expected.
10 |
11 | This is demo'd here by having the raycasted mouse location tracked with a `red circle`, and the expected raycast location tracked with a `black reticle`.
12 |
13 | ## Without Offsetting
14 |
15 | 
16 |
17 | ## With Offsetting
18 |
19 | 
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pointer-lock-controls-offset-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@react-three/drei": "^2.2.7",
7 | "react": "^17.0.1",
8 | "react-dom": "^17.0.1",
9 | "react-scripts": "4.0.1",
10 | "react-three-fiber": "^5.3.6",
11 | "three": "^0.123.0"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build"
16 | },
17 | "eslintConfig": {
18 | "extends": [
19 | "react-app",
20 | "react-app/jest"
21 | ]
22 | },
23 | "browserslist": {
24 | "production": [
25 | ">0.2%",
26 | "not dead",
27 | "not op_mini all"
28 | ],
29 | "development": [
30 | "last 1 chrome version",
31 | "last 1 firefox version",
32 | "last 1 safari version"
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [],
5 | "start_url": ".",
6 | "display": "standalone",
7 | "theme_color": "#000000",
8 | "background_color": "#ffffff"
9 | }
10 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/readmeAssets/withOffsetting2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacobJaffe/pointer-lock-controls-offset-example/1a2d4b6881dca11879f7bd11f2a6d87d4f8a8231/readmeAssets/withOffsetting2.gif
--------------------------------------------------------------------------------
/readmeAssets/withoutOffsetting2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacobJaffe/pointer-lock-controls-offset-example/1a2d4b6881dca11879f7bd11f2a6d87d4f8a8231/readmeAssets/withoutOffsetting2.gif
--------------------------------------------------------------------------------
/src/Box.js:
--------------------------------------------------------------------------------
1 | // https://codesandbox.io/s/rrppl0y8l4?file=/src/App.js:1095-1166
2 |
3 | import React, { useRef, useState } from "react";
4 | import { useFrame } from "react-three-fiber";
5 |
6 | function Box(props) {
7 | // This reference will give us direct access to the mesh
8 | const mesh = useRef()
9 | // Set up state for the hovered and active state
10 | const [hovered, setHover] = useState(false)
11 | const [active, setActive] = useState(false)
12 | // Rotate mesh every frame, this is outside of React without overhead
13 | useFrame(() => {
14 | mesh.current.rotation.x = mesh.current.rotation.y += 0.01
15 | })
16 | return (
17 | setActive(!active)}
22 | onPointerOver={(e) => setHover(true)}
23 | onPointerOut={(e) => setHover(false)}>
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default Box;
--------------------------------------------------------------------------------
/src/Reticle.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 | import * as THREE from "three";
3 |
4 | const SIZE = 0.01;
5 |
6 | const Reticle = () => {
7 | const vertices = useMemo(
8 | () =>
9 | [
10 | [0, SIZE, 0],
11 | [0, -SIZE, 0],
12 | [0, 0, 0],
13 | [SIZE, 0, 0],
14 | [-SIZE, 0, 0],
15 | ].map((v) => new THREE.Vector3(...v)),
16 | []
17 | );
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Reticle;
28 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import ReactDOM from "react-dom";
3 | import { Canvas, useThree, useFrame } from "react-three-fiber";
4 | import { PointerLockControls, PerspectiveCamera } from "@react-three/drei";
5 | import Reticle from "./Reticle";
6 | import "./styles.css";
7 | import Box from "./Box";
8 | import { Vector3 } from "three";
9 |
10 | function App() {
11 | const controlsRef = useRef();
12 | const isLocked = useRef(false);
13 |
14 | return (
15 |
54 | );
55 | }
56 |
57 |
58 | function Camera() {
59 | const mouseReticle = useRef();
60 | const { camera, mouse } = useThree();
61 | // initialize camera to look at origin.
62 | useEffect(() => {
63 | camera.lookAt(0, 0, 0);
64 | }, [camera]);
65 |
66 | return (
67 |
68 | {/* This reticle lives where the camera is pointing.
69 | This would be used in a first person environment */}
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | // Project a reticle of the mouse position onto the near plane.
78 | // A little janky because the rotation is off, so it gets cut.
79 | function MouseReticle() {
80 | const { camera, mouse } = useThree();
81 | const mouseReticle = useRef();
82 |
83 | useFrame(() => {
84 | if (mouseReticle.current) {
85 | const vector = new Vector3(mouse.x, mouse.y, -0.8).unproject(camera);
86 | mouseReticle.current.position.set(...vector.toArray());
87 | }
88 | })
89 |
90 | return (
91 |
92 |
93 |
94 |
95 | )
96 | }
97 |
98 | const rootElement = document.getElementById("root");
99 | ReactDOM.render(, rootElement);
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html,
6 | body,
7 | #root {
8 | width: 100%;
9 | height: 100%;
10 | margin: 0;
11 | padding: 0;
12 | }
--------------------------------------------------------------------------------