Skip to content

Commit

Permalink
docs: handheld-ar, hit test, anchor examples
Browse files Browse the repository at this point in the history
  • Loading branch information
bbohlender committed Aug 1, 2024
1 parent c9cd905 commit e796aa3
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/handheld-ar/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
42 changes: 42 additions & 0 deletions examples/handheld-ar/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Canvas, createPortal, useThree } from '@react-three/fiber'
import { createXRStore, XR, XRDomOverlay, XROrigin } from '@react-three/xr'
import { useEffect, useState } from 'react'
import {} from '@react-three/drei'

const store = createXRStore({})

export function App() {
const [bool, setBool] = useState(false)
return (
<>
<button onClick={() => store.enterVR()}>Enter VR</button>
<button onClick={() => store.enterAR()}>Enter AR</button>
<Canvas style={{ width: '100%', flexGrow: 1 }}>
<XR store={store}>
<XROrigin />
<ambientLight />

<ViewerBox />
</XR>
</Canvas>
</>
)
}

function ViewerBox() {
const camera = useThree((s) => s.camera)
const scene = useThree((s) => s.scene)
const [red, setRed] = useState(false)
return (
<>
<primitive object={camera} />
{createPortal(
<mesh onClick={() => setRed((r) => !r)} position-z={-5}>
<boxGeometry />
<meshBasicMaterial color={red ? 'red' : 'green'} />
</mesh>,
camera,
)}
</>
)
}
12 changes: 12 additions & 0 deletions examples/handheld-ar/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script async type="module" src="./index.tsx"></script>
</head>
<body style="touch-action: none; margin: 0; position: relative; width: 100dvw; height: 100dvh; overflow: hidden;">
<div id="root" style="position: absolute; inset: 0; display: flex; flex-direction: column;"></div>
</body>
</html>
9 changes: 9 additions & 0 deletions examples/handheld-ar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createRoot } from 'react-dom/client'
import { App } from './app.js'
import { StrictMode } from 'react'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
9 changes: 9 additions & 0 deletions examples/handheld-ar/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dependencies": {
"@react-three/drei": "^9.109.2",
"@react-three/xr": "workspace:^"
},
"scripts": {
"dev": "vite --host"
}
}
13 changes: 13 additions & 0 deletions examples/handheld-ar/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
import path from 'path'
import react from '@vitejs/plugin-react'
import basicSsl from '@vitejs/plugin-basic-ssl'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), basicSsl()],
resolve: {
alias: [{ find: '@react-three/xr', replacement: path.resolve(__dirname, '../../packages/react/xr/src/index.ts') }],
dedupe: ['@react-three/fiber', 'three'],
},
})
1 change: 1 addition & 0 deletions examples/hit-test-anchor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
147 changes: 147 additions & 0 deletions examples/hit-test-anchor/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Canvas } from '@react-three/fiber'
import {
XRSpace,
createXRStore,
XR,
XRHitTest,
useXRControllerState,
XRControllerModel,
XRHandModel,
useXRHandState,
GetWorldMatrixFromXRHitTest,
useXRScreenInputState,
useXRInputSourceEvent,
useXRRequestHitTest,
useXRAnchor,
} from '@react-three/xr'
import { useEffect, useRef } from 'react'
import { Matrix4, Mesh, Vector3 } from 'three'
import { create } from 'zustand'

const matrixHelper = new Matrix4()

function onResults(handedness: XRHandedness, results: XRHitTestResult[], getWorldMatrix: GetWorldMatrixFromXRHitTest) {
;(handedness === 'left' ? useLeftPoints : useRightPoints).setState(
results
.map((result) => {
if (!getWorldMatrix(matrixHelper, result)) {
return undefined
}
return new Vector3().setFromMatrixPosition(matrixHelper)
})
.filter((vector) => vector != null),
)
}

const store = createXRStore({
hand: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const state = useXRHandState()
return (
<>
<XRHandModel />
<XRSpace space={state.inputSource.targetRaySpace}>
<XRHitTest onResults={onResults.bind(null, state.inputSource.handedness)} />
</XRSpace>
</>
)
},
controller: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const state = useXRHandState()
return (
<>
<XRControllerModel />
<XRSpace space={state.inputSource.targetRaySpace}>
<XRHitTest onResults={onResults.bind(null, state.inputSource.handedness)} />
</XRSpace>
</>
)
},
screenInput: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const state = useXRScreenInputState()
return (
<>
<XRSpace space={state.inputSource.targetRaySpace}>
<XRHitTest onResults={onResults.bind(null, state.inputSource.handedness)} />
</XRSpace>
</>
)
},
})

const useLeftPoints = create<Array<Vector3>>(() => [])
const useRightPoints = create<Array<Vector3>>(() => [])

export function App() {
const leftLength = useLeftPoints((s) => s.length)
const rightLength = useRightPoints((s) => s.length)
return (
<>
<button onClick={() => store.enterAR()}>Enter AR</button>
<Canvas style={{ width: '100%', flexGrow: 1 }}>
<XR store={store}>
<ambientLight />
{new Array(leftLength).fill(undefined).map((_, i) => (
<Point left key={i} index={i} />
))}
{new Array(rightLength).fill(undefined).map((_, i) => (
<Point key={i} index={i} />
))}
<Anchors />
</XR>
</Canvas>
</>
)
}

function Anchors() {
const [anchor, requestAnchor] = useXRAnchor()
const requestHitTest = useXRRequestHitTest()
const controllerState = useXRControllerState('right')
const handState = useXRHandState('right')
const inputSource = controllerState?.inputSource ?? handState?.inputSource
useXRInputSourceEvent(
inputSource,
'select',
async () => {
if (inputSource == null) {
return
}
const result = await requestHitTest(inputSource.targetRaySpace)
if (result == null || result.results.length === 0) {
return
}
requestAnchor({ relativeTo: 'hit-test-result', hitTestResult: result.results[0] })
},
[requestHitTest, requestAnchor, inputSource],
)
if (anchor == null) {
return null
}
return (
<XRSpace space={anchor.anchorSpace}>
<mesh scale={0.1}>
<boxGeometry />
</mesh>
</XRSpace>
)
}

function Point({ index, left }: { left?: boolean; index: number }) {
const ref = useRef<Mesh>(null)
useEffect(
() =>
(left ? useLeftPoints : useRightPoints).subscribe((state) => {
ref.current!.position.copy(state[index])
}),
[index, left],
)
return (
<mesh scale={0.05} ref={ref}>
<sphereGeometry />
<meshBasicMaterial />
</mesh>
)
}
12 changes: 12 additions & 0 deletions examples/hit-test-anchor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script async type="module" src="./index.tsx"></script>
</head>
<body style="touch-action: none; margin: 0; position: relative; width: 100dvw; height: 100dvh; overflow: hidden;">
<div id="root" style="position: absolute; inset: 0; display: flex; flex-direction: column;"></div>
</body>
</html>
9 changes: 9 additions & 0 deletions examples/hit-test-anchor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createRoot } from 'react-dom/client'
import { App } from './app.js'
import { StrictMode } from 'react'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
9 changes: 9 additions & 0 deletions examples/hit-test-anchor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dependencies": {
"@react-three/xr": "workspace:^",
"zustand": "^4.5.2"
},
"scripts": {
"dev": "vite --host"
}
}
13 changes: 13 additions & 0 deletions examples/hit-test-anchor/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
import path from 'path'
import react from '@vitejs/plugin-react'
import basicSsl from '@vitejs/plugin-basic-ssl'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), basicSsl()],
resolve: {
alias: [{ find: '@react-three/xr', replacement: path.resolve(__dirname, '../../packages/react/xr/src/index.ts') }],
dedupe: ['@react-three/fiber', 'three'],
},
})
1 change: 0 additions & 1 deletion examples/room-with-shadows/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export default function App() {
</button>
<Canvas shadows camera={{ position: [5, 2, 10], fov: 50 }}>
<XR store={store}>
<PerformanceMonitor onDecline={() => set(true)} />
<SoftShadows />
<CameraControls makeDefault />
<color attach="background" args={['#d0d0d0']} />
Expand Down
Loading

0 comments on commit e796aa3

Please sign in to comment.