Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(post-processing): depth of field effect #56

Merged
merged 16 commits into from
Sep 28, 2023
68 changes: 18 additions & 50 deletions src/core/effects/DepthOfField.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts" setup>
import type { BlendFunction } from 'postprocessing'
import { EffectPass, DepthOfFieldEffect, BlendMode } from 'postprocessing'
import { useTresContext } from '@tresjs/core'
import { EffectPass, DepthOfFieldEffect } from 'postprocessing'
import { inject, onUnmounted, shallowRef, watchEffect } from 'vue'
import { makePropWatchers } from '../../util/prop'
import { effectComposerInjectionKey } from '../injectionKeys'

export interface DepthOfFieldProps {
Expand Down Expand Up @@ -64,54 +65,21 @@ const unwatch = watchEffect(() => {
composer?.value?.addPass(pass.value)
})

watchEffect(() => {
if (!effect.value) return
const plainEffectPass = new DepthOfFieldEffect()

// blendFunction is not updated, because it has no setter in BloomEffect

effect.value.circleOfConfusionMaterial.worldFocusDistance
= props.worldFocusDistance !== undefined
? props.worldFocusDistance
: plainEffectPass.circleOfConfusionMaterial.worldFocusDistance

effect.value.circleOfConfusionMaterial.worldFocusRange
= props.worldFocusRange !== undefined
? props.worldFocusRange
: plainEffectPass.circleOfConfusionMaterial.worldFocusRange

effect.value.circleOfConfusionMaterial.focusRange
= props.focusRange !== undefined
? props.focusRange
: plainEffectPass.circleOfConfusionMaterial.focusRange

effect.value.circleOfConfusionMaterial.focusDistance
= props.focusDistance !== undefined
? props.focusDistance
: plainEffectPass.circleOfConfusionMaterial.focusDistance

effect.value.bokehScale
= props.bokehScale !== undefined
? props.bokehScale
: plainEffectPass.bokehScale

effect.value.blurPass.resolution.scale
= props.resolutionScale !== undefined
? props.resolutionScale
: plainEffectPass.blurPass.resolution.scale

effect.value.blurPass.resolution.width
= props.resolutionX !== undefined
? props.resolutionX
: plainEffectPass.resolution.width

effect.value.blurPass.resolution.height
= props.resolutionX !== undefined
? props.resolutionX
: plainEffectPass.resolution.height

plainEffectPass.dispose()
})
makePropWatchers(
Tinoooo marked this conversation as resolved.
Show resolved Hide resolved
[
// blendFunction is not updated, because it has no setter in BloomEffect
[ () => props.worldFocusDistance, 'circleOfConfusionMaterial.worldFocusDistance'],
[ () => props.focusDistance, 'circleOfConfusionMaterial.focusDistance' ],
[ () => props.worldFocusRange, 'circleOfConfusionMaterial.worldFocusRange'],
[ () => props.focusRange, 'circleOfConfusionMaterial.focusRange'],
[ () => props.bokehScale, 'bokehScale'],
[ () => props.resolutionScale, 'blurPass.resolution.scale'],
[ () => props.resolutionX, 'resolution.width'],
[ () => props.resolutionX, 'resolution.height'],
],
effect,
() => new DepthOfFieldEffect(),
)

onUnmounted(() => {
if (pass.value) composer?.value?.removePass(pass.value)
Expand All @@ -120,4 +88,4 @@ onUnmounted(() => {
})
</script>

<template></template>
<template></template>
21 changes: 21 additions & 0 deletions src/util/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const pathRegex = /([^[.\]])+/g

export const get = <T>(obj: any, path: string | string[]): T | undefined => {
if (!path) return undefined

const pathArray = Array.isArray(path) ? path : path.match(pathRegex)

return pathArray?.reduce((prevObj, key) => prevObj && prevObj[key], obj)
}

export const set = (obj: any, path: string | string[], value: any): void => {
const pathArray = Array.isArray(path) ? path : path.match(pathRegex)

if (pathArray)
pathArray.reduce((acc, key, i) => {
if (acc[key] === undefined) acc[key] = {}
if (i === pathArray.length - 1) acc[key] = value
return acc[key]
}, obj)
}

51 changes: 51 additions & 0 deletions src/util/prop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Ref } from 'vue'
import { watch } from 'vue'
import { set, get } from './object'

/**
* Creates a prop watcher function that monitors changes to a property and updates a target object.
*
* @template T - The type of the property being watched.
* @template E - The type of the target object.
* @param {() => T} propGetter - A function that retrieves the prop value to be watched.
* @param {Ref<E>} target - A Ref representing the target object to be updated.
* @param {string} propertyPath - The dot-separated path to the property within the target object.
* @param {() => E & { dispose?(): void }} newPlainObjectFunction - A function that creates a new plain object to retrieve the defaults from with an optional "dispose" method for cleanup.
*/
export const makePropWatcher = <T, E>(
propGetter: () => T,
target: Ref<E>,
propertyPath: string,
newPlainObjectFunction: () => E & { dispose?(): void },
) =>
watch(propGetter, (newValue) => {
if (!target.value) return

if (newValue === undefined) {
const plainObject = newPlainObjectFunction()

set(target.value, propertyPath, get(plainObject, propertyPath))

plainObject.dispose?.()
}
else
set(target.value, propertyPath, propGetter())
})

/**
* Creates multiple prop watchers for monitoring changes to multiple properties and updating a target object.
*
* @template T - The type of the property being watched.
* @template E - The type of the target object.
* @param {(string | (() => T))[][]} propGettersAndPropertyPaths - An array of arrays containing pairs of prop getters and their corresponding property paths within the target object.
* @param {Ref<E>} target - A Ref representing the target object to be updated.
* @param {() => E & { dispose?(): void }} newPlainObjectFunction - A function that creates a new plain object to retrieve the defaults from with an optional "dispose" method for cleanup.
*/
export const makePropWatchers = <T, E>(
propGettersAndPropertyPaths: (string | (() => T))[][],
target: Ref<E>,
newPlainObjectFunction: () => E & { dispose?(): void },
) =>
propGettersAndPropertyPaths.map(([propGetterFn, path]) => makePropWatcher(
propGetterFn as () => T, target, path as string, newPlainObjectFunction,
))