Skip to content

Commit

Permalink
Add Hands component, rework how controllers are loaded, add useContro…
Browse files Browse the repository at this point in the history
…ller hook, add Hands example, add examples folder
  • Loading branch information
sniok committed Aug 30, 2020
1 parent edfc33b commit 70f4230
Show file tree
Hide file tree
Showing 18 changed files with 13,638 additions and 144 deletions.
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ React components and hooks for creating VR/AR applications with [react-three-fib
<a href="https://codesandbox.io/s/react-xr-paddle-demo-v4uet"><img width="390" src="https://i.imgur.com/K71D3Ts.gif" /></a>
<a href="https://codesandbox.io/s/react-xr-simple-demo-8i9ro"><img width="390" src="https://i.imgur.com/5yh7LKz.gif" /></a>
<a href="https://codesandbox.io/s/react-xr-simple-ar-demo-8w8hm"><img height="221" src="https://i.imgur.com/yuNwPpn.gif" /></a>
<a href="https://codesandbox.io/s/react-xr-hands-demo-gczkp"><img height="221" src="https://i.imgur.com/CyeevrA.gif" /></a>
</p>
<p align="middle">
<i>These demos are real, you can click them! They contain the full code, too.</i>
Expand Down Expand Up @@ -89,7 +90,7 @@ Controllers is an array of `XRController` objects
interface XRController {
grip: Group
controller: Group
inputSource?: XRInputSource
inputSource: XRInputSource
// ...
// more in XRController.ts
}
Expand Down Expand Up @@ -119,6 +120,23 @@ const onSqueeze = useCallback(() => console.log('Left controller squeeze'), [])
useXREvent('squeeze', onSqueeze, { handedness: 'left' })
```
### useControllers
Use this hook to get n instance of the controller
```jsx
const leftController = useController('left')
```
### `<Hands>`
Add hands model for hand-tracking. Currently only works on Oculus Quest with #webxr-hands experimental flag enabled
```jsx
<VRCanvas>
<Hands />
```
### Interactions
`react-xr` comes with built-in high level interaction components.
Expand All @@ -145,7 +163,7 @@ useXREvent('squeeze', onSqueeze, { handedness: 'left' })
## Getting the VR Camera (HMD) Location
To get the position of the VR camera, use three's WebXRManager instance.
To get the position of the VR camera, use three's WebXRManager instance.
```jsx
const { camera } = useThree()
Expand All @@ -159,7 +177,7 @@ If you want to attach the user to an object so it can be moved around, just pare
```jsx
const mesh = useRef()
const { gl, camera } = useThree()

useEffect(() => {
const cam = gl.xr.isPresenting ? gl.xr.getCamera(camera) : camera
mesh.current.add(cam)
Expand All @@ -169,7 +187,7 @@ useEffect(() => {
// bundle add the controllers to the same object as the camera so it all stays together.
const { controllers } = useXR()
useEffect(() => {
if (controllers.length > 0) controllers.forEach(c => mesh.current.add(c.grip))
return () => controllers.forEach(c => mesh.current.remove(c.grip))
if (controllers.length > 0) controllers.forEach((c) => mesh.current.add(c.grip))
return () => controllers.forEach((c) => mesh.current.remove(c.grip))
}, [controllers, mesh])
```
1 change: 1 addition & 0 deletions examples/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SKIP_PREFLIGHT_CHECK=true
23 changes: 23 additions & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
12 changes: 12 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Explore Examples

You can explore additional examples locally:

```shell
git clone https://github.com/react-spring/react-xr
cd react-xr
yarn
cd examples
yarn
yarn start
```
25 changes: 25 additions & 0 deletions examples/config-overrides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { addWebpackAlias, removeModuleScopePlugin, babelInclude, override } = require('customize-cra')
const { addReactRefresh } = require('customize-cra-react-refresh')
const path = require('path')

module.exports = (config, env) => {
config.resolve.extensions = [...config.resolve.extensions, '.ts', '.tsx']
return override(
addReactRefresh(),
removeModuleScopePlugin(),
babelInclude([path.resolve('src'), path.resolve('../src')]),
addWebpackAlias({
'react-three-fiber': path.resolve('node_modules/react-three-fiber'),
'react-xr': path.resolve('../src/'),
react: path.resolve('node_modules/react'),
'react-dom': path.resolve('node_modules/react-dom'),
scheduler: path.resolve('node_modules/scheduler'),
'react-scheduler': path.resolve('node_modules/react-scheduler'),
'prop-types': path.resolve('node_modules/prop-types'),
three: path.resolve('node_modules/three'),
// three$: path.res olve('node_modules/three/src/Three'),
//three$: path.resolve('./resources/three.js'),
// '../../../build/three.module.js': path.resolve('./resources/three.js'),
})
)(config, env)
}
76 changes: 76 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"name": "examples-cra",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.3.0",
"@testing-library/react": "^10.0.2",
"@testing-library/user-event": "^10.0.1",
"cannon": "^0.6.2",
"drei": "^0.0.64",
"pseudo-worker": "^1.3.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-merge-refs": "^1.0.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
"react-spring": "^8.0.27",
"react-three-fiber": "^4.2.17",
"react-three-gui": "^0.1.5",
"react-use-gesture": "^7.0.9",
"styled-components": "^5.0.1",
"three": "^0.119.1",
"threejs-meshline": "^2.0.10",
"use-cannon": "https://github.com/react-spring/use-cannon.git",
"zustand": "^2.2.3"
},
"scripts": {
"start": "HTTPS=true react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"prettier": {
"semi": false,
"trailingComma": "es5",
"singleQuote": true,
"jsxBracketSameLine": true,
"tabWidth": 2,
"printWidth": 120
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"git add"
]
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/preset-typescript": "^7.9.0",
"customize-cra": "^0.9.1",
"customize-cra-react-refresh": "^1.0.1",
"husky": "^4.2.3",
"lint-staged": "^10.1.2",
"prettier": "^2.0.2",
"react-app-rewired": "^2.1.5"
}
}
20 changes: 20 additions & 0 deletions examples/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react-xr examples</title>
<style>
#root {
height: 100vh;
width: 100vw;
}
body {
margin: 0;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
148 changes: 148 additions & 0 deletions examples/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import ReactDOM from 'react-dom'
import React, { useState, useEffect, useRef, Suspense, useMemo, useCallback } from 'react'
import { VRCanvas, useXREvent, DefaultXRControllers, Hands, Select, Hover, useXR } from 'react-xr'
import { useFrame, useThree } from 'react-three-fiber'
import { OrbitControls, Sky, Text, Plane, Box } from 'drei'
import { Color, Box3, BufferGeometry, Vector3, TextureLoader, ImageLoader } from 'three/build/three.module'

function Key({ name, pos = [0, 0], onClick, width = 1, ...rest }) {
const meshRef = useRef()

const { gl } = useThree()

const leftHand = gl.xr.getHand(0)

const focused = useRef(false)
useFrame(() => {
if (!meshRef.current) {
return
}
const leftTip = leftHand.joints[9]
if (leftTip === undefined) {
return
}

const box = new Box3().setFromObject(meshRef.current)

if (box.containsPoint(leftTip.position)) {
if (!focused.current) {
onClick()
focused.current = true
meshRef.current.material.color = new Color(0x444444)
}
} else {
if (focused.current) {
meshRef.current.material.color = new Color(0xffffff)
focused.current = false
}
}
})

const keySize = 0.018
const keyWidth = width * keySize
const keyGap = 0.004
const size = keySize + keyGap

const xpi = (pos[0] / 10) * Math.PI
const offset = 0.01 - Math.sin(xpi) / 20

const position = [size * pos[0], -size * pos[1], offset]

return (
<group {...rest} position={position} rotation={[0, Math.cos(xpi) / 2, 0]}>
<mesh ref={meshRef}>
<boxBufferGeometry attach="geometry" args={[keyWidth, keySize, 0.02]} />
<meshStandardMaterial attach="material" color="#fafafa" />
</mesh>
<Text position={[0, 0.003, keySize / 1.7]} fontSize={0.015} color="#333">
{name}
</Text>
</group>
)
}

function Keyboard() {
const [state, setState] = useState({
text: '',
focused: ' ',
})

const { gl } = useThree()

useEffect(() => {
const rightHand = gl.xr.getHand(1)

const onPinch = () => {
setState((it) => {
const text = it.focused === 'backspace' ? it.text.substring(0, it.text.length - 1) : it.text + it.focused
return {
text,
focused: it.focused,
}
})
}
rightHand.addEventListener('pinchstart', onPinch)

return () => {
rightHand.removeEventListener('pinchstart', onPinch)
}
}, [gl, setState])

const onClick = (key) => () => setState((it) => ({ ...it, focused: key }))

return (
<group position={[-0.2, 1.14, -0.4]} rotation={[0, 0, 0]}>
<Text position={[0.15, 0.1, 0]} fontSize={0.03}>
{state.text + '|'}
</Text>
{['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'].map((key, i) => (
<Key name={key} pos={[i - 0.2, 0]} onClick={onClick(key)} />
))}
{['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'].map((key, i) => (
<Key name={key} pos={[i, 1]} onClick={onClick(key)} />
))}
{['z', 'x', 'c', 'v', 'b', 'n', 'm'].map((key, i) => (
<Key name={key} pos={[i + 0.4, 2]} onClick={onClick(key)} />
))}
<Key name={' '} pos={[3, 3]} onClick={onClick(' ')} width={5} />
<Key name={'<'} pos={[8, 3]} onClick={onClick('backspace')} />
</group>
)
}

function Button(props) {
const [hover, setHover] = useState(false)
const [color, setColor] = useState(0x123456)

const onSelect = useCallback(() => {
setColor((Math.random() * 0xffffff) | 0)
}, [setColor])

return (
<Select onSelect={onSelect}>
<Hover onChange={setHover}>
<Box scale={hover ? [1.5, 1.5, 1.5] : [1, 1, 1]} args={[0.4, 0.1, 0.1]} {...props}>
<meshStandardMaterial attach="material" color={color} />
<Text position={[0, 0, 0.06]} fontSize={0.05} color="#000" anchorX="center" anchorY="middle">
Hello react-xr!
</Text>
</Box>
</Hover>
</Select>
)
}

function App() {
return (
<VRCanvas>
<ambientLight intensity={0.5} />
<pointLight position={[5, 5, 5]} />

<Hands />
<DefaultXRControllers />
<Button position={[0, 0.8, -1]} />
</VRCanvas>
)
}

ReactDOM.render(<App />, document.querySelector('#root'))
Loading

0 comments on commit 70f4230

Please sign in to comment.