feat(tresjs): added tresjs package with new Tresjs v5 integrated post-processing components
Fix #605 - https://github.com/Tresjs/post-processing/issues/212 Co-authored-by: Iro <155815508+Iro96@users.noreply.github.com>
This commit is contained in:
@@ -74,6 +74,7 @@ words:
|
|||||||
- dompurify
|
- dompurify
|
||||||
- dotenvx
|
- dotenvx
|
||||||
- DownloadLive2DSDK
|
- DownloadLive2DSDK
|
||||||
|
- Downsampling
|
||||||
- dreamlog
|
- dreamlog
|
||||||
- dtolnay
|
- dtolnay
|
||||||
- dtype
|
- dtype
|
||||||
@@ -157,6 +158,7 @@ words:
|
|||||||
- mousedown
|
- mousedown
|
||||||
- mouseup
|
- mouseup
|
||||||
- msvc
|
- msvc
|
||||||
|
- multisampling
|
||||||
- Myriam
|
- Myriam
|
||||||
- ndarray
|
- ndarray
|
||||||
- Neko
|
- Neko
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^5.4.1",
|
"@antfu/eslint-config": "^5.4.1",
|
||||||
"@antfu/ni": "^26.0.1",
|
|
||||||
"@arethetypeswrong/core": "^0.18.2",
|
"@arethetypeswrong/core": "^0.18.2",
|
||||||
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
||||||
"@iconify/utils": "^3.0.2",
|
"@iconify/utils": "^3.0.2",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@proj-airi/stage-ui-three",
|
"name": "@proj-airi/stage-ui-three",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"private": true,
|
||||||
"description": "A collection of 3D scene components that used by Project AIRI",
|
"description": "A collection of 3D scene components that used by Project AIRI",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Moeru AI Project AIRI Team",
|
"name": "Moeru AI Project AIRI Team",
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
"@pixiv/three-vrm": "^3.4.2",
|
"@pixiv/three-vrm": "^3.4.2",
|
||||||
"@pixiv/three-vrm-animation": "^3.4.2",
|
"@pixiv/three-vrm-animation": "^3.4.2",
|
||||||
"@pixiv/three-vrm-core": "^3.4.2",
|
"@pixiv/three-vrm-core": "^3.4.2",
|
||||||
|
"@proj-airi/tresjs": "workspace:*",
|
||||||
"@tresjs/cientos": "^5.0.0",
|
"@tresjs/cientos": "^5.0.0",
|
||||||
"@tresjs/core": "^5.0.2",
|
"@tresjs/core": "^5.0.2",
|
||||||
"@tresjs/post-processing": "^2.4.0",
|
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
"culori": "^4.0.2",
|
"culori": "^4.0.2",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/*
|
/*
|
||||||
* ThreeScene.vue
|
|
||||||
* - Root vue component of stage-ui-three package
|
* - Root vue component of stage-ui-three package
|
||||||
* - This scene component the root for all the sub components in the 3d scene
|
* - This scene component the root for all the sub components in the 3d scene
|
||||||
* - This package, stage-ui-three, is a stateful package
|
* - This package, stage-ui-three, is a stateful package
|
||||||
@@ -13,8 +12,8 @@ import type { DirectionalLight, SphericalHarmonics3, Texture, WebGLRenderTarget
|
|||||||
|
|
||||||
import type { Vec3 } from '../stores/model-store'
|
import type { Vec3 } from '../stores/model-store'
|
||||||
|
|
||||||
|
import { EffectComposerPmndrs, HueSaturationPmndrs } from '@proj-airi/tresjs/post-processing'
|
||||||
import { TresCanvas } from '@tresjs/core'
|
import { TresCanvas } from '@tresjs/core'
|
||||||
import { EffectComposerPmndrs, HueSaturationPmndrs } from '@tresjs/post-processing'
|
|
||||||
import { useElementBounding } from '@vueuse/core'
|
import { useElementBounding } from '@vueuse/core'
|
||||||
import { formatHex } from 'culori'
|
import { formatHex } from 'culori'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
@@ -283,6 +282,7 @@ defineExpose({
|
|||||||
:height="height"
|
:height="height"
|
||||||
:tone-mapping="ACESFilmicToneMapping"
|
:tone-mapping="ACESFilmicToneMapping"
|
||||||
:tone-mapping-exposure="1"
|
:tone-mapping-exposure="1"
|
||||||
|
:clear-alpha="0"
|
||||||
@ready="onTresReady"
|
@ready="onTresReady"
|
||||||
>
|
>
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ vrmDialog.onChange(handleAddVRMModel)
|
|||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
class="will-change-[opacity,transform] z-10000 max-w-45 rounded-lg p-0.5 shadow-md outline-none data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=top]:animate-slideDownAndFade"
|
class="will-change-[opacity,transform] z-10000 max-w-45 rounded-lg p-0.5 shadow-md outline-none data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=top]:animate-slideDownAndFade"
|
||||||
bg="neutral-700/50 dark:neutral-950/50"
|
bg="neutral-100/50 dark:neutral-950/50"
|
||||||
transition="colors duration-200 ease-in-out"
|
transition="colors duration-200 ease-in-out"
|
||||||
backdrop-blur-sm
|
backdrop-blur-sm
|
||||||
align="end"
|
align="end"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@proj-airi/tresjs",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"description": "Shared Tres (effects, composables)",
|
||||||
|
"author": {
|
||||||
|
"name": "Moeru AI Project AIRI Team",
|
||||||
|
"email": "airi@moeru.ai",
|
||||||
|
"url": "https://github.com/moeru-ai"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/moeru-ai/airi.git",
|
||||||
|
"directory": "packages/tres"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
"./post-processing": "./src/post-processing/index.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "vue-tsc --noEmit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tresjs/cientos": ">=5.0.0 <6.0.0",
|
||||||
|
"@tresjs/core": ">=5.0.0 <6.0.0",
|
||||||
|
"postprocessing": ">=6.0.0 <7.0.0",
|
||||||
|
"three": ">=0.180.0 <0.181.0",
|
||||||
|
"vue": ">=3.5.0 <4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/three": "^0.180.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<!--
|
||||||
|
https://github.com/Tresjs/post-processing/blob/c89b5b4353d889d58da4dd6746e64b32a3f711ec/src/core/pmndrs/EffectComposerPmndrs.vue
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { EffectComposer } from 'postprocessing'
|
||||||
|
import type { WebGLRenderer } from 'three'
|
||||||
|
import type { InjectionKey, ShallowRef } from 'vue'
|
||||||
|
|
||||||
|
import WEBGL from 'three/examples/jsm/capabilities/WebGL.js'
|
||||||
|
|
||||||
|
import { useTres, useTresContext } from '@tresjs/core'
|
||||||
|
import {
|
||||||
|
DepthDownsamplingPass,
|
||||||
|
EffectComposer as EffectComposerImpl,
|
||||||
|
NormalPass,
|
||||||
|
RenderPass,
|
||||||
|
} from 'postprocessing'
|
||||||
|
import { HalfFloatType } from 'three'
|
||||||
|
import { computed, onUnmounted, provide, shallowRef, watch } from 'vue'
|
||||||
|
|
||||||
|
export const effectComposerInjectionKey: InjectionKey<ShallowRef<EffectComposer | null>> = Symbol('effectComposerPmndrs')
|
||||||
|
|
||||||
|
export interface EffectComposerPmndrsProps {
|
||||||
|
enabled?: boolean
|
||||||
|
depthBuffer?: boolean
|
||||||
|
disableNormalPass?: boolean
|
||||||
|
stencilBuffer?: boolean
|
||||||
|
resolutionScale?: number
|
||||||
|
autoClear?: boolean
|
||||||
|
multisampling?: number
|
||||||
|
frameBufferType?: number
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(defineProps<EffectComposerPmndrsProps>(), {
|
||||||
|
enabled: true,
|
||||||
|
autoClear: true,
|
||||||
|
frameBufferType: HalfFloatType,
|
||||||
|
disableNormalPass: false,
|
||||||
|
depthBuffer: undefined,
|
||||||
|
multisampling: 0,
|
||||||
|
stencilBuffer: undefined,
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['render'])
|
||||||
|
|
||||||
|
const { renderer } = useTresContext()
|
||||||
|
const { scene, camera, sizes } = useTres()
|
||||||
|
|
||||||
|
const effectComposer: ShallowRef<EffectComposerImpl | null> = shallowRef(null)
|
||||||
|
|
||||||
|
let downSamplingPass: DepthDownsamplingPass | null = null
|
||||||
|
let normalPass: NormalPass | null = null
|
||||||
|
|
||||||
|
provide(effectComposerInjectionKey, effectComposer)
|
||||||
|
defineExpose({ composer: effectComposer })
|
||||||
|
|
||||||
|
function setNormalPass() {
|
||||||
|
if (!effectComposer.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
normalPass = new NormalPass(scene.value, camera.value)
|
||||||
|
normalPass.enabled = false
|
||||||
|
effectComposer.value.addPass(normalPass)
|
||||||
|
if (props.resolutionScale !== undefined && WEBGL.isWebGL2Available()) {
|
||||||
|
downSamplingPass = new DepthDownsamplingPass({
|
||||||
|
normalBuffer: normalPass.texture,
|
||||||
|
resolutionScale: props.resolutionScale,
|
||||||
|
})
|
||||||
|
|
||||||
|
downSamplingPass.enabled = false
|
||||||
|
effectComposer.value.addPass(downSamplingPass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectComposerParams = computed(() => {
|
||||||
|
const plainEffectComposer = new EffectComposerImpl()
|
||||||
|
const params = {
|
||||||
|
depthBuffer: props.depthBuffer !== undefined
|
||||||
|
? props.depthBuffer
|
||||||
|
: plainEffectComposer.inputBuffer.depthBuffer,
|
||||||
|
stencilBuffer:
|
||||||
|
props.stencilBuffer !== undefined
|
||||||
|
? props.stencilBuffer
|
||||||
|
: plainEffectComposer.inputBuffer.stencilBuffer,
|
||||||
|
multisampling: WEBGL.isWebGL2Available()
|
||||||
|
? props.multisampling !== undefined
|
||||||
|
? props.multisampling
|
||||||
|
: plainEffectComposer.multisampling
|
||||||
|
: 0,
|
||||||
|
frameBufferType: props.frameBufferType !== undefined
|
||||||
|
? props.frameBufferType
|
||||||
|
: HalfFloatType,
|
||||||
|
}
|
||||||
|
|
||||||
|
plainEffectComposer.dispose()
|
||||||
|
|
||||||
|
return params
|
||||||
|
})
|
||||||
|
|
||||||
|
function initEffectComposer() {
|
||||||
|
if (!renderer && !scene.value && !camera.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
effectComposer.value?.dispose()
|
||||||
|
effectComposer.value = new EffectComposerImpl(renderer.instance as WebGLRenderer, effectComposerParams.value)
|
||||||
|
effectComposer.value.addPass(new RenderPass(scene.value, camera.value))
|
||||||
|
|
||||||
|
if (!props.disableNormalPass) {
|
||||||
|
setNormalPass()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([scene, camera, () => props.disableNormalPass], () => {
|
||||||
|
if (!sizes.width.value || !sizes.height.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initEffectComposer()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => [sizes.width.value, sizes.height.value], ([width, height]) => {
|
||||||
|
// effect composer should only live once the canvas has a size > 0
|
||||||
|
if (!width && !height) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectComposer.value) {
|
||||||
|
effectComposer.value.setSize(width, height)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
initEffectComposer()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
renderer.replaceRenderFunction((notifySuccess) => {
|
||||||
|
if (props.enabled && renderer.instance && effectComposer.value && sizes.width.value && sizes.height.value) {
|
||||||
|
const currentAutoClear = renderer.instance.autoClear
|
||||||
|
renderer.instance.autoClear = props.autoClear
|
||||||
|
if (props.stencilBuffer && !props.autoClear) {
|
||||||
|
renderer.instance.clearStencil()
|
||||||
|
}
|
||||||
|
|
||||||
|
effectComposer.value.render()
|
||||||
|
emit('render', effectComposer.value)
|
||||||
|
renderer.instance.autoClear = currentAutoClear
|
||||||
|
notifySuccess()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
effectComposer.value?.dispose()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { BlendFunction } from 'postprocessing'
|
||||||
|
|
||||||
|
import { HueSaturationEffect } from 'postprocessing'
|
||||||
|
|
||||||
|
import { useEffectPmndrs } from '../../composables/use-effect-pmndrs'
|
||||||
|
import { makePropWatchers } from '../../utils'
|
||||||
|
|
||||||
|
export interface HueSaturationPmndrsProps {
|
||||||
|
/**
|
||||||
|
* The saturation adjustment. A value of 0.0 results in grayscale, and 1.0 leaves saturation unchanged.
|
||||||
|
* Range: [0.0, 1.0]
|
||||||
|
*/
|
||||||
|
saturation?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hue adjustment in radians.
|
||||||
|
* Range: [-π, π] (or [0, 2π] for a full rotation)
|
||||||
|
*/
|
||||||
|
hue?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The blend function. Defines how the effect blends with the original scene.
|
||||||
|
*/
|
||||||
|
blendFunction?: BlendFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<HueSaturationPmndrsProps>()
|
||||||
|
|
||||||
|
const { pass, effect } = useEffectPmndrs(() => new HueSaturationEffect(props), props)
|
||||||
|
|
||||||
|
defineExpose({ pass, effect })
|
||||||
|
|
||||||
|
makePropWatchers(
|
||||||
|
[
|
||||||
|
[() => props.blendFunction, 'blendMode.blendFunction'],
|
||||||
|
[() => props.hue, 'hue'],
|
||||||
|
[() => props.saturation, 'saturation'],
|
||||||
|
],
|
||||||
|
effect,
|
||||||
|
() => new HueSaturationEffect(),
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as HueSaturationPmndrs } from './HueSaturationPmndrs.vue'
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as EffectComposerPmndrs } from './EffectComposerPmndrs.vue'
|
||||||
|
export { HueSaturationPmndrs } from './effects'
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { useEffectPmndrs } from './use-effect-pmndrs'
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* https://github.com/Tresjs/post-processing/blob/c89b5b4353d889d58da4dd6746e64b32a3f711ec/src/core/pmndrs/EffectComposerPmndrs.vue
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Effect } from 'postprocessing'
|
||||||
|
import type { Reactive, ShallowRef } from 'vue'
|
||||||
|
|
||||||
|
import { useTres } from '@tresjs/core'
|
||||||
|
import { EffectPass } from 'postprocessing'
|
||||||
|
import { inject, nextTick, onUnmounted, shallowRef, watch, watchEffect } from 'vue'
|
||||||
|
|
||||||
|
import { effectComposerInjectionKey } from '../components/EffectComposerPmndrs.vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param newEffectFunction - A function that returns a new effect instance.
|
||||||
|
* @param passDependencies - A reactive object that the pass depends on (usually props). Changes to this object will trigger re-rendering.
|
||||||
|
* @param dependencyFieldsTriggeringRecreation - fields in passDependencies that require effect recreation when changed
|
||||||
|
*/
|
||||||
|
export function useEffectPmndrs<T extends Effect, D extends Record<PropertyKey, any>>(newEffectFunction: () => T, passDependencies: Reactive<D>, dependencyFieldsTriggeringRecreation?: (keyof D)[]): {
|
||||||
|
pass: ShallowRef<EffectPass | null>
|
||||||
|
effect: ShallowRef<T | null>
|
||||||
|
} {
|
||||||
|
const composer = inject(effectComposerInjectionKey)
|
||||||
|
|
||||||
|
const pass = shallowRef<EffectPass | null>(null) as ShallowRef<EffectPass | null>
|
||||||
|
const effect = shallowRef<T | null>(null) as ShallowRef<T | null>
|
||||||
|
|
||||||
|
const { scene, camera, invalidate } = useTres()
|
||||||
|
|
||||||
|
watch(passDependencies, () => invalidate())
|
||||||
|
|
||||||
|
const removePass = () => {
|
||||||
|
if (pass.value) {
|
||||||
|
composer?.value?.removePass(pass.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.value?.dispose()
|
||||||
|
pass.value?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const createEffect = (index?: number) => {
|
||||||
|
if (!camera.value || !composer?.value || !scene.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.value = newEffectFunction()
|
||||||
|
pass.value = new EffectPass(camera.value, effect.value)
|
||||||
|
|
||||||
|
composer.value.addPass(pass.value, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for changes in props that require effect recreation
|
||||||
|
if (dependencyFieldsTriggeringRecreation) {
|
||||||
|
watch(
|
||||||
|
() => dependencyFieldsTriggeringRecreation.map(field => passDependencies[field]),
|
||||||
|
() => {
|
||||||
|
if (!composer?.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = composer.value?.passes.findIndex(p => p === pass.value)
|
||||||
|
if (!~index) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
removePass()
|
||||||
|
createEffect(index)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (!camera.value || !effect?.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.value.mainCamera = camera.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initial effect creation
|
||||||
|
const unwatch = watchEffect(() => {
|
||||||
|
if (!camera.value || !composer?.value || !scene.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => unwatch())
|
||||||
|
if (effect.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
removePass()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
effect,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export {
|
||||||
|
EffectComposerPmndrs,
|
||||||
|
HueSaturationPmndrs,
|
||||||
|
} from './components'
|
||||||
|
export {
|
||||||
|
useEffectPmndrs,
|
||||||
|
} from './composables'
|
||||||
|
export {
|
||||||
|
get,
|
||||||
|
makePropWatcher,
|
||||||
|
makePropWatchers,
|
||||||
|
makePropWatchersUsingAllProps,
|
||||||
|
omit,
|
||||||
|
set,
|
||||||
|
} from './utils'
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { get, omit, set } from './object'
|
||||||
|
export { makePropWatcher, makePropWatchers, makePropWatchersUsingAllProps } from './props'
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
const pathRegex = /([^[.\]])+/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the value at a given path within a provided object.
|
||||||
|
*
|
||||||
|
* @template T - The type of value to be returned
|
||||||
|
*
|
||||||
|
* @param {any} obj - The object to extract value from
|
||||||
|
* @param {string | string[]} path - A path or an array of path where the value should be get from
|
||||||
|
*
|
||||||
|
* @returns {T | undefined} - The value at the given path in the object, or undefined if path is not found
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* const obj = { a: { b: { c: 1 } } }
|
||||||
|
*
|
||||||
|
* const result = get(obj, 'a.b.c')
|
||||||
|
*
|
||||||
|
* console.log(result) // 1
|
||||||
|
*/
|
||||||
|
export function 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value at a given path within a provided object. If the path does not exist, nested objects will be created.
|
||||||
|
*
|
||||||
|
* @param {any} obj - The original object to set value in
|
||||||
|
* @param {string | string[]} path - A path or an array of path where the value should be set
|
||||||
|
* @param {any} value - The value to be set at the provided path
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const obj = { a: { b: { c: 1 } } }
|
||||||
|
*
|
||||||
|
* set(obj, 'a.b.c', 2)
|
||||||
|
*
|
||||||
|
* console.log(obj) // { a: { b: { c: 2 } } }
|
||||||
|
*/
|
||||||
|
export function 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Omits given properties from a provided object.
|
||||||
|
*
|
||||||
|
* @template T - An object with string keys and any type of values
|
||||||
|
*
|
||||||
|
* @param {T} obj - The original object to omit properties from
|
||||||
|
* @param {(keyof T)[]} properties - An array of property key names to omit from the base object
|
||||||
|
*
|
||||||
|
* @returns {Partial<T>} The new object with omitted properties
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const obj = { a: 1, b: 2, c: 3 }
|
||||||
|
* const propsToOmit = ['b', 'c']
|
||||||
|
*
|
||||||
|
* const newObj = omit(obj, propsToOmit)
|
||||||
|
*
|
||||||
|
* console.log(newObj) // { a: 1 }
|
||||||
|
*/
|
||||||
|
export function omit<T extends Record<string, any>>(obj: T, properties: (keyof T)[]): Partial<T> {
|
||||||
|
const newObj = { ...obj }
|
||||||
|
properties.forEach(prop => delete newObj[prop])
|
||||||
|
return newObj
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import type { Ref, WatchOptions } from 'vue'
|
||||||
|
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
|
import { get, set } 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.
|
||||||
|
* @param {WatchOptions} watchOptions - The options for watch.
|
||||||
|
*/
|
||||||
|
export function makePropWatcher<T, E>(
|
||||||
|
propGetter: () => T,
|
||||||
|
target: Ref<E>,
|
||||||
|
propertyPath: string,
|
||||||
|
newPlainObjectFunction: () => E & { dispose?: () => void },
|
||||||
|
watchOptions: WatchOptions = {},
|
||||||
|
) {
|
||||||
|
return 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())
|
||||||
|
}
|
||||||
|
}, watchOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 function makePropWatchers<E>(
|
||||||
|
propGettersAndPropertyPaths: (string | (() => any))[][],
|
||||||
|
target: Ref<E>,
|
||||||
|
newPlainObjectFunction: () => E & { dispose?: () => void },
|
||||||
|
) {
|
||||||
|
return propGettersAndPropertyPaths.map(([propGetterFn, path]) => makePropWatcher(
|
||||||
|
propGetterFn as () => any,
|
||||||
|
target,
|
||||||
|
path as string,
|
||||||
|
newPlainObjectFunction,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates multiple prop watchers via the props object for monitoring changes to multiple properties and updating a target object.
|
||||||
|
* Use this method in case the prop names match the names of the properties you want to set on your target object.
|
||||||
|
*
|
||||||
|
* @param props - The props object. Usually created via defineProps.
|
||||||
|
* @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 function makePropWatchersUsingAllProps<E>(
|
||||||
|
props: { [key: PropertyKey]: any },
|
||||||
|
target: Ref<E>,
|
||||||
|
newPlainObjectFunction: () => E & { dispose?: () => void },
|
||||||
|
) {
|
||||||
|
return Object.keys(props).map(key => makePropWatcher(
|
||||||
|
() => props[key],
|
||||||
|
target,
|
||||||
|
key,
|
||||||
|
newPlainObjectFunction,
|
||||||
|
))
|
||||||
|
}
|
||||||
Generated
+25
-6
@@ -79,9 +79,6 @@ importers:
|
|||||||
'@antfu/eslint-config':
|
'@antfu/eslint-config':
|
||||||
specifier: ^5.4.1
|
specifier: ^5.4.1
|
||||||
version: 5.4.1(@unocss/eslint-plugin@66.5.2(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.36.0(jiti@2.5.1)))(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@2.5.1)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(less@4.4.1)(lightningcss@1.30.1)(msw@2.7.3(@types/node@24.5.2)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
version: 5.4.1(@unocss/eslint-plugin@66.5.2(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.36.0(jiti@2.5.1)))(eslint@9.36.0(jiti@2.5.1))(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.2)(jiti@2.5.1)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(less@4.4.1)(lightningcss@1.30.1)(msw@2.7.3(@types/node@24.5.2)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||||
'@antfu/ni':
|
|
||||||
specifier: ^26.0.1
|
|
||||||
version: 26.0.1
|
|
||||||
'@arethetypeswrong/core':
|
'@arethetypeswrong/core':
|
||||||
specifier: ^0.18.2
|
specifier: ^0.18.2
|
||||||
version: 0.18.2
|
version: 0.18.2
|
||||||
@@ -1985,15 +1982,15 @@ importers:
|
|||||||
'@pixiv/three-vrm-core':
|
'@pixiv/three-vrm-core':
|
||||||
specifier: ^3.4.2
|
specifier: ^3.4.2
|
||||||
version: 3.4.2(three@0.180.0)
|
version: 3.4.2(three@0.180.0)
|
||||||
|
'@proj-airi/tresjs':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../tresjs
|
||||||
'@tresjs/cientos':
|
'@tresjs/cientos':
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.0(@tresjs/core@5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2)))(@types/three@0.180.0)(react@18.3.1)(three@0.180.0)(vue@3.5.22(typescript@5.9.2))
|
version: 5.0.0(@tresjs/core@5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2)))(@types/three@0.180.0)(react@18.3.1)(three@0.180.0)(vue@3.5.22(typescript@5.9.2))
|
||||||
'@tresjs/core':
|
'@tresjs/core':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2))
|
version: 5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2))
|
||||||
'@tresjs/post-processing':
|
|
||||||
specifier: ^2.4.0
|
|
||||||
version: 2.4.0(@tresjs/core@5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2)))(three@0.180.0)(typescript@5.9.2)(vue@3.5.22(typescript@5.9.2))
|
|
||||||
'@vueuse/core':
|
'@vueuse/core':
|
||||||
specifier: ^13.9.0
|
specifier: ^13.9.0
|
||||||
version: 13.9.0(vue@3.5.22(typescript@5.9.2))
|
version: 13.9.0(vue@3.5.22(typescript@5.9.2))
|
||||||
@@ -2033,6 +2030,28 @@ importers:
|
|||||||
specifier: ^2.15.4
|
specifier: ^2.15.4
|
||||||
version: 2.15.4(typescript@5.9.2)
|
version: 2.15.4(typescript@5.9.2)
|
||||||
|
|
||||||
|
packages/tresjs:
|
||||||
|
dependencies:
|
||||||
|
'@tresjs/cientos':
|
||||||
|
specifier: '>=5.0.0 <6.0.0'
|
||||||
|
version: 5.0.0(@tresjs/core@5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2)))(@types/three@0.180.0)(react@18.3.1)(three@0.180.0)(vue@3.5.22(typescript@5.9.2))
|
||||||
|
'@tresjs/core':
|
||||||
|
specifier: '>=5.0.0 <6.0.0'
|
||||||
|
version: 5.0.2(three@0.180.0)(vue@3.5.22(typescript@5.9.2))
|
||||||
|
postprocessing:
|
||||||
|
specifier: '>=6.0.0 <7.0.0'
|
||||||
|
version: 6.37.8(three@0.180.0)
|
||||||
|
three:
|
||||||
|
specifier: '>=0.180.0 <0.181.0'
|
||||||
|
version: 0.180.0
|
||||||
|
vue:
|
||||||
|
specifier: '>=3.5.0 <4.0.0'
|
||||||
|
version: 3.5.22(typescript@5.9.2)
|
||||||
|
devDependencies:
|
||||||
|
'@types/three':
|
||||||
|
specifier: ^0.180.0
|
||||||
|
version: 0.180.0
|
||||||
|
|
||||||
packages/ui:
|
packages/ui:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vueuse/core':
|
'@vueuse/core':
|
||||||
|
|||||||
Reference in New Issue
Block a user