Skip to content

Commit 9d1f77e

Browse files
authored
feat: Autofocus effect (#192)
* feat(Autofocus): new effect * fix1 * fix2: mdx doc * tweaks * fixes review drcmda
1 parent cd7008c commit 9d1f77e

File tree

5 files changed

+198
-0
lines changed

5 files changed

+198
-0
lines changed

docs/effects/autofocus.mdx

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
title: Autofocus
3+
nav: 1
4+
---
5+
6+
An auto-focus effect, that extends `<DepthOfField>`.
7+
8+
Based on [ektogamat/AutoFocusDOF](https://github.com/ektogamat/AutoFocusDOF).
9+
10+
```tsx
11+
export type AutofocusProps = typeof DepthOfField & {
12+
target?: [number, number, number] // undefined
13+
mouse?: boolean // false
14+
debug?: number // undefined
15+
manual?: boolean // false
16+
smoothTime?: number // .25
17+
}
18+
```
19+
20+
```tsx
21+
<EffectComposer>
22+
<Autofocus />
23+
</EffectComposer>
24+
```
25+
26+
Ref-api:
27+
28+
```tsx
29+
type AutofocusApi = {
30+
dofRef: RefObject<DepthOfFieldEffect>
31+
hitpoint: THREE.Vector3
32+
update: (delta: number, updateTarget: boolean) => void
33+
}
34+
```
35+
36+
```tsx
37+
<Autofocus ref={autofocusRef} />
38+
```
39+
40+
Associated with `manual` prop, you can for example, animate the DOF target yourself:
41+
42+
```tsx
43+
useFrame((_, delta) => {
44+
const api = autofocusRef.current
45+
api.update(delta, false) // update hitpoint only
46+
easing.damp3(api.dofRef.curent.target, api.hitpoint, 0.5, delta) // custom easing
47+
})
48+
```
49+
50+
## Example
51+
52+
<Codesandbox id="dfw6w4" />

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"release": "semantic-release"
4646
},
4747
"dependencies": {
48+
"maath": "^0.5.3",
4849
"postprocessing": "^6.30.2",
4950
"screen-space-reflections": "2.5.0",
5051
"three-stdlib": "^2.21.10"

src/Autofocus.tsx

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import * as THREE from 'three'
2+
import React, {
3+
useRef,
4+
useContext,
5+
useState,
6+
useEffect,
7+
useCallback,
8+
forwardRef,
9+
useImperativeHandle,
10+
RefObject,
11+
} from 'react'
12+
import { useThree, useFrame, createPortal } from '@react-three/fiber'
13+
import { CopyPass, DepthPickingPass, DepthOfFieldEffect } from 'postprocessing'
14+
import { DepthOfField, EffectComposerContext } from './index'
15+
import { easing } from 'maath'
16+
17+
export type AutofocusProps = typeof DepthOfField & {
18+
target?: [number, number, number]
19+
mouse?: boolean
20+
debug?: number
21+
manual?: boolean
22+
smoothTime?: number
23+
}
24+
25+
export type AutofocusApi = {
26+
dofRef: RefObject<DepthOfFieldEffect>
27+
hitpoint: THREE.Vector3
28+
update: (delta: number, updateTarget: boolean) => void
29+
}
30+
31+
export const Autofocus = forwardRef<AutofocusApi, AutofocusProps>(
32+
(
33+
{ target = undefined, mouse: followMouse = false, debug = undefined, manual = false, smoothTime = 0, ...props },
34+
fref
35+
) => {
36+
const dofRef = useRef<DepthOfFieldEffect>(null)
37+
const hitpointRef = useRef<THREE.Mesh>(null)
38+
const targetRef = useRef<THREE.Mesh>(null)
39+
40+
const scene = useThree(({ scene }) => scene)
41+
const pointer = useThree(({ pointer }) => pointer)
42+
const { composer, camera } = useContext(EffectComposerContext)
43+
44+
// see: https://codesandbox.io/s/depthpickingpass-x130hg
45+
const [depthPickingPass] = useState(new DepthPickingPass())
46+
const [copyPass] = useState(new CopyPass())
47+
useEffect(() => {
48+
composer.addPass(depthPickingPass)
49+
composer.addPass(copyPass)
50+
return () => {
51+
composer.removePass(depthPickingPass)
52+
composer.removePass(copyPass)
53+
}
54+
}, [composer, depthPickingPass, copyPass])
55+
56+
const [hitpoint] = useState(new THREE.Vector3(0, 0, 0))
57+
58+
const [ndc] = useState(new THREE.Vector3(0, 0, 0))
59+
const getHit = useCallback(
60+
async (x: number, y: number) => {
61+
ndc.x = x
62+
ndc.y = y
63+
ndc.z = await depthPickingPass.readDepth(ndc)
64+
ndc.z = ndc.z * 2.0 - 1.0
65+
const hit = 1 - ndc.z > 0.0000001 // it is missed if ndc.z is close to 1
66+
return hit ? ndc.unproject(camera) : false
67+
},
68+
[ndc, depthPickingPass, camera]
69+
)
70+
71+
const update = useCallback(
72+
async (delta: number, updateTarget = true) => {
73+
// Update hitpoint
74+
if (target) {
75+
hitpoint.set(...target)
76+
} else {
77+
const { x, y } = followMouse ? pointer : { x: 0, y: 0 }
78+
const hit = await getHit(x, y)
79+
if (hit) hitpoint.copy(hit)
80+
}
81+
82+
// Update target
83+
if (updateTarget && dofRef.current?.target) {
84+
if (smoothTime > 0 && delta > 0) {
85+
easing.damp3(dofRef.current.target, hitpoint, smoothTime, delta)
86+
} else {
87+
dofRef.current.target.copy(hitpoint)
88+
}
89+
}
90+
},
91+
[target, hitpoint, followMouse, getHit, smoothTime, pointer]
92+
)
93+
94+
useFrame(async (_, delta) => {
95+
if (!manual) {
96+
update(delta)
97+
}
98+
if (hitpointRef.current) {
99+
hitpointRef.current.position.copy(hitpoint)
100+
}
101+
if (targetRef.current && dofRef.current?.target) {
102+
targetRef.current.position.copy(dofRef.current.target)
103+
}
104+
})
105+
106+
// Ref API
107+
useImperativeHandle(
108+
fref,
109+
() => ({
110+
dofRef,
111+
hitpoint,
112+
update,
113+
}),
114+
[hitpoint, update]
115+
)
116+
117+
return (
118+
<>
119+
{debug
120+
? createPortal(
121+
<>
122+
<mesh ref={hitpointRef}>
123+
<sphereGeometry args={[debug, 16, 16]} />
124+
<meshBasicMaterial color="#00ff00" opacity={1} transparent depthWrite={false} />
125+
</mesh>
126+
<mesh ref={targetRef}>
127+
<sphereGeometry args={[debug / 2, 16, 16]} />
128+
<meshBasicMaterial color="#00ff00" opacity={0.5} transparent depthWrite={false} />
129+
</mesh>
130+
</>,
131+
scene
132+
)
133+
: null}
134+
135+
<DepthOfField ref={dofRef} {...props} target={hitpoint} />
136+
</>
137+
)
138+
}
139+
)

src/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ export * from './effects/TiltShift2'
3030
export * from './effects/SSR'
3131

3232
export * from './Selection'
33+
export * from './Autofocus'
3334
export * from './EffectComposer'
3435
export * from './util'

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -3338,6 +3338,11 @@ lru-cache@^9.0.0:
33383338
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
33393339
integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==
33403340

3341+
maath@^0.5.3:
3342+
version "0.5.3"
3343+
resolved "https://registry.yarnpkg.com/maath/-/maath-0.5.3.tgz#777a1f9b8463c6ffb199ea43406874a357c0cd58"
3344+
integrity sha512-ut63A4zTd9abtpi+sOHW1fPWPtAFrjK0E17eAthx1k93W/T2cWLKV5oaswyotJVDvvW1EXSdokAqhK5KOu0Qdw==
3345+
33413346
magic-string@^0.27.0:
33423347
version "0.27.0"
33433348
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"

0 commit comments

Comments
 (0)