Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions src/Selection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import * as THREE from 'three'
import React, { createContext, useState, useContext, useEffect, useRef, useMemo } from 'react'
import { type ThreeElements } from '@react-three/fiber'
import {
createContext,
useContext,
useEffect,
useMemo,
useRef,
useState,
type Dispatch,
type ReactNode,
type SetStateAction,
} from 'react'
import { type Group, type Object3D } from 'three'

export type Api = {
selected: THREE.Object3D[]
select: React.Dispatch<React.SetStateAction<THREE.Object3D[]>>
selected: Object3D[]
select: Dispatch<SetStateAction<Object3D[]>>
enabled: boolean
}
export type SelectApi = Omit<ThreeElements['group'], 'ref'> & {
Expand All @@ -13,19 +23,19 @@ export type SelectApi = Omit<ThreeElements['group'], 'ref'> & {

export const selectionContext = /* @__PURE__ */ createContext<Api | null>(null)

export function Selection({ children, enabled = true }: { enabled?: boolean; children: React.ReactNode }) {
const [selected, select] = useState<THREE.Object3D[]>([])
export function Selection({ children, enabled = true }: { enabled?: boolean; children: ReactNode }) {
const [selected, select] = useState<Object3D[]>([])
const value = useMemo(() => ({ selected, select, enabled }), [selected, select, enabled])
return <selectionContext.Provider value={value}>{children}</selectionContext.Provider>
}

export function Select({ enabled = false, children, ...props }: SelectApi) {
const group = useRef<THREE.Group>(null!)
const group = useRef<Group>(null!)
const api = useContext(selectionContext)
useEffect(() => {
if (api && enabled) {
let changed = false
const current: THREE.Object3D[] = []
const current: Object3D[] = []
group.current.traverse((o) => {
o.type === 'Mesh' && current.push(o)
if (api.selected.indexOf(o) === -1) changed = true
Expand Down
241 changes: 123 additions & 118 deletions src/effects/Autofocus.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import * as THREE from 'three'
import React, {
useRef,
import { createPortal, useFrame, useThree, type Vector3 as R3FVector3 } from '@react-three/fiber'
import { easing } from 'maath'
import { CopyPass, DepthOfFieldEffect, DepthPickingPass } from 'postprocessing'
import {
Ref,
useCallback,
useContext,
useState,
useEffect,
useCallback,
forwardRef,
useImperativeHandle,
RefObject,
useMemo,
useRef,
useState,
type ComponentProps,
type RefObject,
} from 'react'
import { useThree, useFrame, createPortal, type Vector3 } from '@react-three/fiber'
import { CopyPass, DepthPickingPass, DepthOfFieldEffect } from 'postprocessing'
import { easing } from 'maath'
import { Mesh, Vector3 } from 'three'

import { DepthOfField } from './DepthOfField'
import { EffectComposerContext } from '../EffectComposer'
import { DepthOfField } from './DepthOfField'

export type AutofocusProps = React.ComponentProps<typeof DepthOfField> & {
target?: Vector3
export type AutofocusProps = ComponentProps<typeof DepthOfField> & {
target?: R3FVector3
/** should the target follow the pointer */
mouse?: boolean
/** size of the debug green point */
Expand All @@ -27,127 +28,131 @@ export type AutofocusProps = React.ComponentProps<typeof DepthOfField> & {
manual?: boolean
/** approximate time to reach the target */
smoothTime?: number
ref?: Ref<AutofocusApi>
}

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

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

const scene = useThree(({ scene }) => scene)
const pointer = useThree(({ pointer }) => pointer)
const { composer, camera } = useContext(EffectComposerContext)
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])
// 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])

useEffect(() => {
return () => {
depthPickingPass.dispose()
copyPass.dispose()
}
}, [depthPickingPass, copyPass])
useEffect(() => {
return () => {
depthPickingPass.dispose()
copyPass.dispose()
}
}, [depthPickingPass, copyPass])

const [hitpoint] = useState(() => new THREE.Vector3(0, 0, 0))
const [hitpoint] = useState(() => new 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 [ndc] = useState(() => new 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 as [number, number, number]))
} else {
const { x, y } = followMouse ? pointer : { x: 0, y: 0 }
const hit = await getHit(x, y)
if (hit) hitpoint.copy(hit)
}
const update = useCallback(
async (delta: number, updateTarget = true) => {
// Update hitpoint
if (target) {
hitpoint.set(...(target as unknown as [number, number, number]))
} 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)
}
// 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)
}
})
},
[target, hitpoint, followMouse, getHit, smoothTime, pointer]
)

// Ref API
const api = useMemo<AutofocusApi>(
() => ({
dofRef,
hitpoint,
update,
}),
[hitpoint, update]
)
useImperativeHandle(fref, () => api, [api])
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)
}
})

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}
// Ref API
const api = useMemo<AutofocusApi>(
() => ({
dofRef,
hitpoint,
update,
}),
[hitpoint, update]
)
useImperativeHandle(ref, () => api, [api])

<DepthOfField ref={dofRef} {...props} target={hitpoint} />
</>
)
}
)
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} />
</>
)
}
4 changes: 2 additions & 2 deletions src/effects/Bloom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BloomEffect, BlendFunction } from 'postprocessing'
import { wrapEffect } from '../util'
import { BlendFunction, BloomEffect } from 'postprocessing'
import { wrapEffect } from '../wrapEffect'

export const Bloom = /* @__PURE__ */ wrapEffect(BloomEffect, {
blendFunction: BlendFunction.ADD,
Expand Down
2 changes: 1 addition & 1 deletion src/effects/BrightnessContrast.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrightnessContrastEffect } from 'postprocessing'
import { wrapEffect } from '../util'
import { wrapEffect } from '../wrapEffect'

export const BrightnessContrast = /* @__PURE__ */ wrapEffect(BrightnessContrastEffect)
2 changes: 1 addition & 1 deletion src/effects/ChromaticAberration.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChromaticAberrationEffect } from 'postprocessing'
import { type EffectProps, wrapEffect } from '../util'
import { type EffectProps, wrapEffect } from '../wrapEffect'

export type ChromaticAberrationProps = EffectProps<typeof ChromaticAberrationEffect>
export const ChromaticAberration = /* @__PURE__ */ wrapEffect(ChromaticAberrationEffect)
2 changes: 1 addition & 1 deletion src/effects/ColorDepth.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ColorDepthEffect } from 'postprocessing'
import { wrapEffect } from '../util'
import { wrapEffect } from '../wrapEffect'

export const ColorDepth = /* @__PURE__ */ wrapEffect(ColorDepthEffect)
2 changes: 1 addition & 1 deletion src/effects/Depth.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DepthEffect } from 'postprocessing'
import { wrapEffect } from '../util'
import { wrapEffect } from '../wrapEffect'

export const Depth = /* @__PURE__ */ wrapEffect(DepthEffect)
2 changes: 1 addition & 1 deletion src/effects/DotScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DotScreenEffect } from 'postprocessing'
import { wrapEffect } from '../util'
import { wrapEffect } from '../wrapEffect'

export const DotScreen = /* @__PURE__ */ wrapEffect(DotScreenEffect)
2 changes: 1 addition & 1 deletion src/effects/FXAA.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FXAAEffect } from 'postprocessing'
import { wrapEffect } from '../util'
import { wrapEffect } from '../wrapEffect'

export const FXAA = /* @__PURE__ */ wrapEffect(FXAAEffect)
2 changes: 1 addition & 1 deletion src/effects/HueSaturation.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HueSaturationEffect } from 'postprocessing'
import { wrapEffect } from '../util'
import { wrapEffect } from '../wrapEffect'

export const HueSaturation = /* @__PURE__ */ wrapEffect(HueSaturationEffect)
Loading
Loading