Skip to content

Commit

Permalink
feat: Autofocus effect (#192)
Browse files Browse the repository at this point in the history
* feat(Autofocus): new effect

* fix1

* fix2: mdx doc

* tweaks

* fixes review drcmda
  • Loading branch information
abernier authored May 2, 2023
1 parent cd7008c commit 9d1f77e
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 0 deletions.
52 changes: 52 additions & 0 deletions docs/effects/autofocus.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Autofocus
nav: 1
---

An auto-focus effect, that extends `<DepthOfField>`.

Based on [ektogamat/AutoFocusDOF](https://github.com/ektogamat/AutoFocusDOF).

```tsx
export type AutofocusProps = typeof DepthOfField & {
target?: [number, number, number] // undefined
mouse?: boolean // false
debug?: number // undefined
manual?: boolean // false
smoothTime?: number // .25
}
```
```tsx
<EffectComposer>
<Autofocus />
</EffectComposer>
```
Ref-api:
```tsx
type AutofocusApi = {
dofRef: RefObject<DepthOfFieldEffect>
hitpoint: THREE.Vector3
update: (delta: number, updateTarget: boolean) => void
}
```
```tsx
<Autofocus ref={autofocusRef} />
```
Associated with `manual` prop, you can for example, animate the DOF target yourself:
```tsx
useFrame((_, delta) => {
const api = autofocusRef.current
api.update(delta, false) // update hitpoint only
easing.damp3(api.dofRef.curent.target, api.hitpoint, 0.5, delta) // custom easing
})
```
## Example
<Codesandbox id="dfw6w4" />
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"release": "semantic-release"
},
"dependencies": {
"maath": "^0.5.3",
"postprocessing": "^6.30.2",
"screen-space-reflections": "2.5.0",
"three-stdlib": "^2.21.10"
Expand Down
139 changes: 139 additions & 0 deletions src/Autofocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as THREE from 'three'
import React, {
useRef,
useContext,
useState,
useEffect,
useCallback,
forwardRef,
useImperativeHandle,
RefObject,
} from 'react'
import { useThree, useFrame, createPortal } from '@react-three/fiber'
import { CopyPass, DepthPickingPass, DepthOfFieldEffect } from 'postprocessing'
import { DepthOfField, EffectComposerContext } from './index'
import { easing } from 'maath'

export type AutofocusProps = typeof DepthOfField & {
target?: [number, number, number]
mouse?: boolean
debug?: number
manual?: boolean
smoothTime?: number
}

export type AutofocusApi = {
dofRef: RefObject<DepthOfFieldEffect>
hitpoint: THREE.Vector3
update: (delta: number, updateTarget: boolean) => void
}

export const Autofocus = forwardRef<AutofocusApi, AutofocusProps>(
(
{ target = undefined, mouse: followMouse = false, debug = undefined, manual = false, smoothTime = 0, ...props },
fref
) => {
const dofRef = useRef<DepthOfFieldEffect>(null)
const hitpointRef = useRef<THREE.Mesh>(null)
const targetRef = useRef<THREE.Mesh>(null)

const scene = useThree(({ scene }) => scene)
const pointer = useThree(({ pointer }) => pointer)
const { composer, camera } = useContext(EffectComposerContext)

// see: https://codesandbox.io/s/depthpickingpass-x130hg
const [depthPickingPass] = useState(new DepthPickingPass())
const [copyPass] = useState(new CopyPass())
useEffect(() => {
composer.addPass(depthPickingPass)
composer.addPass(copyPass)
return () => {
composer.removePass(depthPickingPass)
composer.removePass(copyPass)
}
}, [composer, depthPickingPass, copyPass])

const [hitpoint] = useState(new THREE.Vector3(0, 0, 0))

const [ndc] = useState(new THREE.Vector3(0, 0, 0))
const getHit = useCallback(
async (x: number, y: number) => {
ndc.x = x
ndc.y = y
ndc.z = await depthPickingPass.readDepth(ndc)
ndc.z = ndc.z * 2.0 - 1.0
const hit = 1 - ndc.z > 0.0000001 // it is missed if ndc.z is close to 1
return hit ? ndc.unproject(camera) : false
},
[ndc, depthPickingPass, camera]
)

const update = useCallback(
async (delta: number, updateTarget = true) => {
// Update hitpoint
if (target) {
hitpoint.set(...target)
} else {
const { x, y } = followMouse ? pointer : { x: 0, y: 0 }
const hit = await getHit(x, y)
if (hit) hitpoint.copy(hit)
}

// Update target
if (updateTarget && dofRef.current?.target) {
if (smoothTime > 0 && delta > 0) {
easing.damp3(dofRef.current.target, hitpoint, smoothTime, delta)
} else {
dofRef.current.target.copy(hitpoint)
}
}
},
[target, hitpoint, followMouse, getHit, smoothTime, pointer]
)

useFrame(async (_, delta) => {
if (!manual) {
update(delta)
}
if (hitpointRef.current) {
hitpointRef.current.position.copy(hitpoint)
}
if (targetRef.current && dofRef.current?.target) {
targetRef.current.position.copy(dofRef.current.target)
}
})

// Ref API
useImperativeHandle(
fref,
() => ({
dofRef,
hitpoint,
update,
}),
[hitpoint, update]
)

return (
<>
{debug
? createPortal(
<>
<mesh ref={hitpointRef}>
<sphereGeometry args={[debug, 16, 16]} />
<meshBasicMaterial color="#00ff00" opacity={1} transparent depthWrite={false} />
</mesh>
<mesh ref={targetRef}>
<sphereGeometry args={[debug / 2, 16, 16]} />
<meshBasicMaterial color="#00ff00" opacity={0.5} transparent depthWrite={false} />
</mesh>
</>,
scene
)
: null}

<DepthOfField ref={dofRef} {...props} target={hitpoint} />
</>
)
}
)
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ export * from './effects/TiltShift2'
export * from './effects/SSR'

export * from './Selection'
export * from './Autofocus'
export * from './EffectComposer'
export * from './util'
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3338,6 +3338,11 @@ lru-cache@^9.0.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==

maath@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/maath/-/maath-0.5.3.tgz#777a1f9b8463c6ffb199ea43406874a357c0cd58"
integrity sha512-ut63A4zTd9abtpi+sOHW1fPWPtAFrjK0E17eAthx1k93W/T2cWLKV5oaswyotJVDvvW1EXSdokAqhK5KOu0Qdw==

magic-string@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
Expand Down

0 comments on commit 9d1f77e

Please sign in to comment.