From 7ea96b7c00ce6ea78c6305bb00d1d3619d167433 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Sun, 28 Sep 2025 14:11:23 +0800 Subject: [PATCH] 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> --- cspell.config.yaml | 2 + package.json | 1 - packages/stage-ui-three/package.json | 4 +- .../src/components/ThreeScene.vue | 4 +- .../Dialogs/model-selector/model-selector.vue | 2 +- packages/tresjs/package.json | 33 ++++ .../components/EffectComposerPmndrs.vue | 163 ++++++++++++++++++ .../effects/HueSaturationPmndrs.vue | 47 +++++ .../components/effects/index.ts | 1 + .../src/post-processing/components/index.ts | 2 + .../src/post-processing/composables/index.ts | 1 + .../composables/use-effect-pmndrs.ts | 102 +++++++++++ packages/tresjs/src/post-processing/index.ts | 15 ++ .../tresjs/src/post-processing/utils/index.ts | 2 + .../src/post-processing/utils/object.ts | 90 ++++++++++ .../tresjs/src/post-processing/utils/props.ts | 83 +++++++++ pnpm-lock.yaml | 31 +++- 17 files changed, 571 insertions(+), 12 deletions(-) create mode 100644 packages/tresjs/package.json create mode 100644 packages/tresjs/src/post-processing/components/EffectComposerPmndrs.vue create mode 100644 packages/tresjs/src/post-processing/components/effects/HueSaturationPmndrs.vue create mode 100644 packages/tresjs/src/post-processing/components/effects/index.ts create mode 100644 packages/tresjs/src/post-processing/components/index.ts create mode 100644 packages/tresjs/src/post-processing/composables/index.ts create mode 100644 packages/tresjs/src/post-processing/composables/use-effect-pmndrs.ts create mode 100644 packages/tresjs/src/post-processing/index.ts create mode 100644 packages/tresjs/src/post-processing/utils/index.ts create mode 100644 packages/tresjs/src/post-processing/utils/object.ts create mode 100644 packages/tresjs/src/post-processing/utils/props.ts diff --git a/cspell.config.yaml b/cspell.config.yaml index 615c0e996..a4ef96f98 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -74,6 +74,7 @@ words: - dompurify - dotenvx - DownloadLive2DSDK + - Downsampling - dreamlog - dtolnay - dtype @@ -157,6 +158,7 @@ words: - mousedown - mouseup - msvc + - multisampling - Myriam - ndarray - Neko diff --git a/package.json b/package.json index 430cfb9da..82a0004b7 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ }, "devDependencies": { "@antfu/eslint-config": "^5.4.1", - "@antfu/ni": "^26.0.1", "@arethetypeswrong/core": "^0.18.2", "@electron-toolkit/eslint-config-ts": "^3.1.0", "@iconify/utils": "^3.0.2", diff --git a/packages/stage-ui-three/package.json b/packages/stage-ui-three/package.json index a442158cc..0cdc8eec3 100644 --- a/packages/stage-ui-three/package.json +++ b/packages/stage-ui-three/package.json @@ -1,7 +1,7 @@ { "name": "@proj-airi/stage-ui-three", "type": "module", - "version": "0.0.1", + "private": true, "description": "A collection of 3D scene components that used by Project AIRI", "author": { "name": "Moeru AI Project AIRI Team", @@ -24,9 +24,9 @@ "@pixiv/three-vrm": "^3.4.2", "@pixiv/three-vrm-animation": "^3.4.2", "@pixiv/three-vrm-core": "^3.4.2", + "@proj-airi/tresjs": "workspace:*", "@tresjs/cientos": "^5.0.0", "@tresjs/core": "^5.0.2", - "@tresjs/post-processing": "^2.4.0", "@vueuse/core": "^13.9.0", "culori": "^4.0.2", "pinia": "^3.0.3", diff --git a/packages/stage-ui-three/src/components/ThreeScene.vue b/packages/stage-ui-three/src/components/ThreeScene.vue index 81487dcbe..471b18f2b 100644 --- a/packages/stage-ui-three/src/components/ThreeScene.vue +++ b/packages/stage-ui-three/src/components/ThreeScene.vue @@ -1,6 +1,5 @@ + + + + diff --git a/packages/tresjs/src/post-processing/components/effects/HueSaturationPmndrs.vue b/packages/tresjs/src/post-processing/components/effects/HueSaturationPmndrs.vue new file mode 100644 index 000000000..a3e01fe6b --- /dev/null +++ b/packages/tresjs/src/post-processing/components/effects/HueSaturationPmndrs.vue @@ -0,0 +1,47 @@ + + + diff --git a/packages/tresjs/src/post-processing/components/effects/index.ts b/packages/tresjs/src/post-processing/components/effects/index.ts new file mode 100644 index 000000000..776084273 --- /dev/null +++ b/packages/tresjs/src/post-processing/components/effects/index.ts @@ -0,0 +1 @@ +export { default as HueSaturationPmndrs } from './HueSaturationPmndrs.vue' diff --git a/packages/tresjs/src/post-processing/components/index.ts b/packages/tresjs/src/post-processing/components/index.ts new file mode 100644 index 000000000..9c8784982 --- /dev/null +++ b/packages/tresjs/src/post-processing/components/index.ts @@ -0,0 +1,2 @@ +export { default as EffectComposerPmndrs } from './EffectComposerPmndrs.vue' +export { HueSaturationPmndrs } from './effects' diff --git a/packages/tresjs/src/post-processing/composables/index.ts b/packages/tresjs/src/post-processing/composables/index.ts new file mode 100644 index 000000000..d3a033d2e --- /dev/null +++ b/packages/tresjs/src/post-processing/composables/index.ts @@ -0,0 +1 @@ +export { useEffectPmndrs } from './use-effect-pmndrs' diff --git a/packages/tresjs/src/post-processing/composables/use-effect-pmndrs.ts b/packages/tresjs/src/post-processing/composables/use-effect-pmndrs.ts new file mode 100644 index 000000000..056b07512 --- /dev/null +++ b/packages/tresjs/src/post-processing/composables/use-effect-pmndrs.ts @@ -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>(newEffectFunction: () => T, passDependencies: Reactive, dependencyFieldsTriggeringRecreation?: (keyof D)[]): { + pass: ShallowRef + effect: ShallowRef +} { + const composer = inject(effectComposerInjectionKey) + + const pass = shallowRef(null) as ShallowRef + const effect = shallowRef(null) as ShallowRef + + 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, + } +} diff --git a/packages/tresjs/src/post-processing/index.ts b/packages/tresjs/src/post-processing/index.ts new file mode 100644 index 000000000..47ad125e2 --- /dev/null +++ b/packages/tresjs/src/post-processing/index.ts @@ -0,0 +1,15 @@ +export { + EffectComposerPmndrs, + HueSaturationPmndrs, +} from './components' +export { + useEffectPmndrs, +} from './composables' +export { + get, + makePropWatcher, + makePropWatchers, + makePropWatchersUsingAllProps, + omit, + set, +} from './utils' diff --git a/packages/tresjs/src/post-processing/utils/index.ts b/packages/tresjs/src/post-processing/utils/index.ts new file mode 100644 index 000000000..a936812fc --- /dev/null +++ b/packages/tresjs/src/post-processing/utils/index.ts @@ -0,0 +1,2 @@ +export { get, omit, set } from './object' +export { makePropWatcher, makePropWatchers, makePropWatchersUsingAllProps } from './props' diff --git a/packages/tresjs/src/post-processing/utils/object.ts b/packages/tresjs/src/post-processing/utils/object.ts new file mode 100644 index 000000000..cd381b953 --- /dev/null +++ b/packages/tresjs/src/post-processing/utils/object.ts @@ -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(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} 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>(obj: T, properties: (keyof T)[]): Partial { + const newObj = { ...obj } + properties.forEach(prop => delete newObj[prop]) + return newObj +} diff --git a/packages/tresjs/src/post-processing/utils/props.ts b/packages/tresjs/src/post-processing/utils/props.ts new file mode 100644 index 000000000..d4fbb65ec --- /dev/null +++ b/packages/tresjs/src/post-processing/utils/props.ts @@ -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} 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( + propGetter: () => T, + target: Ref, + 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} 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( + propGettersAndPropertyPaths: (string | (() => any))[][], + target: Ref, + 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} 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( + props: { [key: PropertyKey]: any }, + target: Ref, + newPlainObjectFunction: () => E & { dispose?: () => void }, +) { + return Object.keys(props).map(key => makePropWatcher( + () => props[key], + target, + key, + newPlainObjectFunction, + )) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 111980f35..44cb570f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,9 +79,6 @@ importers: '@antfu/eslint-config': 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)) - '@antfu/ni': - specifier: ^26.0.1 - version: 26.0.1 '@arethetypeswrong/core': specifier: ^0.18.2 version: 0.18.2 @@ -1985,15 +1982,15 @@ importers: '@pixiv/three-vrm-core': specifier: ^3.4.2 version: 3.4.2(three@0.180.0) + '@proj-airi/tresjs': + specifier: workspace:* + version: link:../tresjs '@tresjs/cientos': 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)) '@tresjs/core': specifier: ^5.0.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': specifier: ^13.9.0 version: 13.9.0(vue@3.5.22(typescript@5.9.2)) @@ -2033,6 +2030,28 @@ importers: specifier: ^2.15.4 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: dependencies: '@vueuse/core':