diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3168e78c7..8dfe41078 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,9 +24,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - with: - # checkout is pinned to dev branch here - ref: dev - name: Setup Node uses: actions/setup-node@v3 with: diff --git a/packages/three-vrm-animation/README.md b/packages/three-vrm-animation/README.md new file mode 100644 index 000000000..c9b5757ff --- /dev/null +++ b/packages/three-vrm-animation/README.md @@ -0,0 +1,9 @@ +# @pixiv/three-vrm-animation + +The implementation of VRM Animation + +[GitHub Repository](https://github.com/pixiv/three-vrm/tree/dev/packages/three-vrm-animation) + +[Examples](https://pixiv.github.io/three-vrm/packages/three-vrm-animation/examples) + +[Documentation](https://pixiv.github.io/three-vrm/packages/three-vrm-animation/docs) diff --git a/packages/three-vrm-animation/examples/.editorconfig b/packages/three-vrm-animation/examples/.editorconfig new file mode 100644 index 000000000..0b225afd4 --- /dev/null +++ b/packages/three-vrm-animation/examples/.editorconfig @@ -0,0 +1,2 @@ +[*] +indent_style = tab diff --git a/packages/three-vrm-animation/examples/.eslintrc b/packages/three-vrm-animation/examples/.eslintrc new file mode 100644 index 000000000..48e061bbc --- /dev/null +++ b/packages/three-vrm-animation/examples/.eslintrc @@ -0,0 +1,11 @@ +{ + "root": true, + "plugins": [ + "html" + ], + "extends": "mdcs", + "rules": { + "padded-blocks":"off", + "no-unused-vars": "off" + } +} diff --git a/packages/three-vrm-animation/examples/dnd.html b/packages/three-vrm-animation/examples/dnd.html new file mode 100644 index 000000000..1af2f326d --- /dev/null +++ b/packages/three-vrm-animation/examples/dnd.html @@ -0,0 +1,281 @@ + + + + + + three-vrm-animation example + + + + + + + + + + + + + + diff --git a/packages/three-vrm-animation/examples/index.html b/packages/three-vrm-animation/examples/index.html new file mode 100644 index 000000000..494a2151d --- /dev/null +++ b/packages/three-vrm-animation/examples/index.html @@ -0,0 +1,24 @@ + + + + + + three-vrm-animation examples + + + + +

examples of @pixiv/three-vrm-animation

+

+ loader-plugin.html
+ Import a VRM Animation from gltf +

+

+ dnd.html
+ A slightly advanced example with a drag-and-drop capability +

+ + diff --git a/packages/three-vrm-animation/examples/loader-plugin.html b/packages/three-vrm-animation/examples/loader-plugin.html new file mode 100644 index 000000000..050f33f71 --- /dev/null +++ b/packages/three-vrm-animation/examples/loader-plugin.html @@ -0,0 +1,151 @@ + + + + + + three-vrm-animation example + + + + + + + + + + + + + + diff --git a/packages/three-vrm-animation/examples/models/VRM1_Constraint_Twist_Sample.vrm b/packages/three-vrm-animation/examples/models/VRM1_Constraint_Twist_Sample.vrm new file mode 100644 index 000000000..8ee8717df Binary files /dev/null and b/packages/three-vrm-animation/examples/models/VRM1_Constraint_Twist_Sample.vrm differ diff --git a/packages/three-vrm-animation/examples/models/test.vrma b/packages/three-vrm-animation/examples/models/test.vrma new file mode 100644 index 000000000..25f0f1b8a Binary files /dev/null and b/packages/three-vrm-animation/examples/models/test.vrma differ diff --git a/packages/three-vrm-animation/package.json b/packages/three-vrm-animation/package.json new file mode 100644 index 000000000..33555518d --- /dev/null +++ b/packages/three-vrm-animation/package.json @@ -0,0 +1,62 @@ +{ + "name": "@pixiv/three-vrm-animation", + "version": "2.0.10", + "description": "The implementation of VRM Animation", + "license": "MIT", + "author": "pixiv", + "files": [ + "/lib/", + "/ts*/", + "/types/", + "LICENSE" + ], + "main": "lib/three-vrm-animation.js", + "module": "lib/three-vrm-animation.module.js", + "types": "types/index.d.ts", + "typesVersions": { + "<3.9": { + "*": [ + "ts3.4/*" + ] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/pixiv/three-vrm.git", + "directory": "packages/three-vrm-animation" + }, + "scripts": { + "version": "yarn all", + "all": "yarn lint && yarn clean && yarn build && yarn docs", + "dev": "cross-env NODE_ENV=development rollup -w -c", + "clean": "rimraf docs/ lib/ ts*/ types/", + "build": "yarn build-dev && yarn build-prod && yarn build-types", + "build-dev": "cross-env NODE_ENV=development rollup -c", + "build-prod": "cross-env NODE_ENV=production rollup -c", + "build-types": "tsc --declaration --declarationDir ./types --emitDeclarationOnly && downlevel-dts types ts3.4/types", + "docs": "typedoc --entryPoints ./src/index.ts --out docs", + "lint": "eslint \"src/**/*.{ts,tsx}\" && yarn lint-examples && prettier \"src/**/*.{ts,tsx}\" --check", + "lint-examples": "eslint \"examples/**/*.{ts,tsx,js,html}\" --rule \"padded-blocks: error\"", + "lint-fix": "eslint \"src/**/*.{ts,tsx}\" --fix && eslint \"examples/**/*.{ts,tsx,js,html}\" --fix && prettier \"src/**/*.{ts,tsx}\" --write" + }, + "lint-staged": { + "./src/**/*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] + }, + "dependencies": { + "@pixiv/three-vrm-core": "2.0.10", + "@pixiv/types-vrmc-vrm-1.0": "2.0.8", + "@pixiv/types-vrmc-vrm-animation-1.0": "2.0.10" + }, + "devDependencies": { + "@types/three": "^0.160.0", + "lint-staged": "13.1.2", + "three": "^0.160.0" + }, + "peerDependencies": { + "@types/three": "^0.160.0", + "three": "^0.160.0" + } +} diff --git a/packages/three-vrm-animation/rollup.config.js b/packages/three-vrm-animation/rollup.config.js new file mode 100644 index 000000000..927b077aa --- /dev/null +++ b/packages/three-vrm-animation/rollup.config.js @@ -0,0 +1,96 @@ +/* eslint-env node */ + +import packageJson from './package.json'; +import serve from 'rollup-plugin-serve'; +import { terser } from 'rollup-plugin-terser'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; + +// == constants ==================================================================================== +/** copyright text */ +const copyright = '(c) 2023 pixiv Inc.'; + +/** name of the license */ +const licenseName = 'MIT License'; + +/** url of the license */ +const licenseUri = 'https://github.com/pixiv/three-vrm/blob/release/LICENSE'; + +/** output name of the module */ +const globalName = 'THREE_VRM_ANIMATION'; + +/** filename of output */ +const filename = 'lib/three-vrm-animation'; + +// == envs ========================================================================================= +const NODE_ENV = process.env.NODE_ENV; +const DEV = NODE_ENV === 'development'; +const WATCH = process.env.ROLLUP_WATCH === 'true'; + +// == banner ======================================================================================= +const bannerTextDev = `/*! + * ${packageJson.name} v${packageJson.version} + * ${packageJson.description} + * + * Copyright ${copyright} + * ${packageJson.name} is distributed under ${licenseName} + * ${licenseUri} + */`; + +const bannerTextProd = `/*! ${copyright} - ${licenseUri} */`; + +// == module ======================================================================================= +/** will be used to inject the stuff into THREE */ +const outro = `Object.assign(THREE, exports);`; + +// == serve ======================================================================================== +const serveOptions = { + contentBase: '.', + port: process.env.PORT ?? 10001, +}; + +// == config ======================================================================================= +function createOutputOptions( { esm } ) { + let file = filename; + file += esm ? '.module' : ''; + file += DEV ? '' : '.min'; + file += '.js'; + + return { + file, + format: esm ? 'esm' : 'umd', + name: esm ? undefined : globalName, + banner: DEV ? bannerTextDev : bannerTextProd, + globals: esm ? undefined : { three: 'THREE' }, + sourcemap: DEV ? 'inline' : false, + plugins: [ + ...( DEV ? [] : [ + terser(), + ] ), + ...( WATCH ? [ + serve( serveOptions ) + ] : [] ), + ], + outro: esm ? undefined : outro, + }; +} + +function createConfig( output ) { + return { + input: 'src/index.ts', + output, + external: [ 'three' ], + plugins: [ + typescript(), + nodeResolve(), + ], + }; +}; + +// == output ======================================================================================= +export default [ + createConfig( [ + createOutputOptions( {} ), + createOutputOptions( { esm: true } ), + ] ), +]; diff --git a/packages/three-vrm-animation/src/VRMAnimation.ts b/packages/three-vrm-animation/src/VRMAnimation.ts new file mode 100644 index 000000000..b09f53683 --- /dev/null +++ b/packages/three-vrm-animation/src/VRMAnimation.ts @@ -0,0 +1,40 @@ +import * as THREE from 'three'; +import type { VRMExpressionPresetName, VRMHumanBoneName } from '@pixiv/three-vrm-core'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { createVRMAnimationClip } from './createVRMAnimationClip'; + +/** + * Represents a single VRM Animation. + * You probably want to create an AnimationClip using {@link createVRMAnimationClip}. + */ +export class VRMAnimation { + public duration: number; + public restHipsPosition: THREE.Vector3; + + public humanoidTracks: { + translation: Map<'hips', THREE.VectorKeyframeTrack>; + rotation: Map; + }; + public expressionTracks: { + preset: Map; + custom: Map; + }; + public lookAtTrack: THREE.QuaternionKeyframeTrack | null; + + public constructor() { + this.duration = 0.0; + this.restHipsPosition = new THREE.Vector3(); + + this.humanoidTracks = { + translation: new Map(), + rotation: new Map(), + }; + + this.expressionTracks = { + preset: new Map(), + custom: new Map(), + }; + + this.lookAtTrack = null; + } +} diff --git a/packages/three-vrm-animation/src/VRMAnimationLoaderPlugin.ts b/packages/three-vrm-animation/src/VRMAnimationLoaderPlugin.ts new file mode 100644 index 000000000..c498fa7e0 --- /dev/null +++ b/packages/three-vrm-animation/src/VRMAnimationLoaderPlugin.ts @@ -0,0 +1,271 @@ +import * as THREE from 'three'; +import { GLTF, GLTFLoaderPlugin, GLTFParser } from 'three/examples/jsm/loaders/GLTFLoader'; +import { GLTF as GLTFSchema } from '@gltf-transform/core'; +import { VRMCVRMAnimation } from '@pixiv/types-vrmc-vrm-animation-1.0'; +import type { VRMHumanBoneName } from '@pixiv/three-vrm-core'; +import { VRMExpressionPresetName, VRMHumanBoneParentMap } from '@pixiv/three-vrm-core'; +import { VRMAnimation } from './VRMAnimation'; +import { arrayChunk } from './utils/arrayChunk'; + +const MAT4_IDENTITY = /*@__PURE__*/ new THREE.Matrix4(); + +const _v3A = /*@__PURE__*/ new THREE.Vector3(); +const _quatA = /*@__PURE__*/ new THREE.Quaternion(); +const _quatB = /*@__PURE__*/ new THREE.Quaternion(); +const _quatC = /*@__PURE__*/ new THREE.Quaternion(); + +/** + * Possible spec versions it recognizes. + */ +const POSSIBLE_SPEC_VERSIONS = /*@__PURE__*/ new Set(['1.0', '1.0-draft']); + +const vrmExpressionPresetNameSet: Set = /*@__PURE__*/ new Set(Object.values(VRMExpressionPresetName)); + +interface VRMAnimationLoaderPluginNodeMap { + humanoidIndexToName: Map; + expressionsIndexToName: Map; + lookAtIndex: number | null; +} + +type VRMAnimationLoaderPluginWorldMatrixMap = Map; + +/** + * A plugin of GLTFLoader that imports {@link VRMAnimation}s from a `VRMC_vrm_animation` extension and gltf animations. + */ +export class VRMAnimationLoaderPlugin implements GLTFLoaderPlugin { + public readonly parser: GLTFParser; + + public constructor(parser: GLTFParser) { + this.parser = parser; + } + + public get name(): string { + return 'VRMC_vrm_animation'; + } + + public async afterRoot(gltf: GLTF): Promise { + const defGltf = gltf.parser.json as GLTFSchema.IGLTF; + const defExtensionsUsed = defGltf.extensionsUsed; + + if (defExtensionsUsed == null || defExtensionsUsed.indexOf(this.name) == -1) { + return; + } + + const defExtension = defGltf.extensions?.[this.name] as VRMCVRMAnimation | undefined; + + if (defExtension == null) { + return; + } + + const specVersion = defExtension.specVersion; + if (!POSSIBLE_SPEC_VERSIONS.has(specVersion)) { + console.warn(`VRMAnimationLoaderPlugin: Unknown VRMC_vrm_animation spec version: ${specVersion}`); + return; + } + if (specVersion === '1.0-draft') { + console.warn( + 'VRMAnimationLoaderPlugin: Using a draft spec version: 1.0-draft. Some behaviors may be different. Consider updating the animation file.', + ); + } + + const nodeMap = this._createNodeMap(defExtension); + const worldMatrixMap = await this._createBoneWorldMatrixMap(gltf, defExtension); + + const hipsNode = defExtension.humanoid?.humanBones['hips']?.node; + const hips = hipsNode != null ? ((await gltf.parser.getDependency('node', hipsNode)) as THREE.Object3D) : null; + + const restHipsPosition = new THREE.Vector3(); + hips?.getWorldPosition(restHipsPosition); + + const clips = gltf.animations; + const animations: VRMAnimation[] = clips.map((clip, iAnimation) => { + const defAnimation = defGltf.animations![iAnimation]; + + const animation = this._parseAnimation(clip, defAnimation, nodeMap, worldMatrixMap); + animation.restHipsPosition = restHipsPosition; + + return animation; + }); + + gltf.userData.vrmAnimations = animations; + } + + private _createNodeMap(defExtension: VRMCVRMAnimation): VRMAnimationLoaderPluginNodeMap { + const humanoidIndexToName: Map = new Map(); + const expressionsIndexToName: Map = new Map(); + + // humanoid + const humanBones = defExtension.humanoid?.humanBones; + + if (humanBones) { + Object.entries(humanBones).forEach(([name, bone]) => { + const node = bone?.node; + if (node != null) { + humanoidIndexToName.set(node, name as VRMHumanBoneName); + } + }); + } + + // expressions + const preset = defExtension.expressions?.preset; + + if (preset) { + Object.entries(preset).forEach(([name, expression]) => { + const node = expression?.node; + if (node != null) { + expressionsIndexToName.set(node, name); + } + }); + } + + const custom = defExtension.expressions?.custom; + + if (custom) { + Object.entries(custom).forEach(([name, expression]) => { + const { node } = expression; + expressionsIndexToName.set(node, name); + }); + } + + // lookAt + const lookAtIndex = defExtension.lookAt?.node ?? null; + + return { humanoidIndexToName, expressionsIndexToName, lookAtIndex }; + } + + private async _createBoneWorldMatrixMap( + gltf: GLTF, + defExtension: VRMCVRMAnimation, + ): Promise { + // update the entire hierarchy first + gltf.scene.updateWorldMatrix(false, true); + + const threeNodes = (await gltf.parser.getDependencies('node')) as THREE.Object3D[]; + + const worldMatrixMap: VRMAnimationLoaderPluginWorldMatrixMap = new Map(); + + for (const [boneName, humanBone] of Object.entries(defExtension.humanoid.humanBones)) { + const node = humanBone?.node; + if (node != null) { + const threeNode = threeNodes[node]; + worldMatrixMap.set(boneName as VRMHumanBoneName, threeNode.matrixWorld); + + if (boneName === 'hips') { + worldMatrixMap.set('hipsParent', threeNode.parent?.matrixWorld ?? MAT4_IDENTITY); + } + } + } + + return worldMatrixMap; + } + + private _parseAnimation( + animationClip: THREE.AnimationClip, + defAnimation: GLTFSchema.IAnimation, + nodeMap: VRMAnimationLoaderPluginNodeMap, + worldMatrixMap: VRMAnimationLoaderPluginWorldMatrixMap, + ): VRMAnimation { + const tracks = animationClip.tracks; + const defChannels = defAnimation.channels; + + const result = new VRMAnimation(); + + result.duration = animationClip.duration; + + defChannels.forEach((channel, iChannel) => { + const { node, path } = channel.target; + const origTrack = tracks[iChannel]; + + if (node == null) { + return; + } + + // humanoid + const boneName = nodeMap.humanoidIndexToName.get(node); + if (boneName != null) { + let parentBoneName: VRMHumanBoneName | 'hipsParent' | null = VRMHumanBoneParentMap[boneName]; + while (parentBoneName != null && worldMatrixMap.get(parentBoneName) == null) { + parentBoneName = VRMHumanBoneParentMap[parentBoneName]; + } + parentBoneName ?? (parentBoneName = 'hipsParent'); + + if (path === 'translation') { + if (boneName !== 'hips') { + console.warn( + `The loading animation contains a translation track for ${boneName}, which is not permitted in the VRMC_vrm_animation spec. ignoring the track`, + ); + } else { + const hipsParentWorldMatrix = worldMatrixMap.get('hipsParent')!; + + const trackValues = arrayChunk(origTrack.values, 3).flatMap((v) => + _v3A.fromArray(v).applyMatrix4(hipsParentWorldMatrix).toArray(), + ); + + const track = origTrack.clone(); + track.values = new Float32Array(trackValues); + + result.humanoidTracks.translation.set(boneName, track); + } + } else if (path === 'rotation') { + // a = p^-1 * a' * p * c + // a' = p * p^-1 * a' * p * c * c^-1 * p^-1 + // = p * a * c^-1 * p^-1 + + const worldMatrix = worldMatrixMap.get(boneName)!; + const parentWorldMatrix = worldMatrixMap.get(parentBoneName)!; + + worldMatrix.decompose(_v3A, _quatA, _v3A); + _quatA.invert(); + + parentWorldMatrix.decompose(_v3A, _quatB, _v3A); + + const trackValues = arrayChunk(origTrack.values, 4).flatMap((q) => + _quatC.fromArray(q).premultiply(_quatB).multiply(_quatA).toArray(), + ); + + const track = origTrack.clone(); + track.values = new Float32Array(trackValues); + + result.humanoidTracks.rotation.set(boneName, track); + } else { + throw new Error(`Invalid path "${path}"`); + } + return; + } + + // expressions + const expressionName = nodeMap.expressionsIndexToName.get(node); + if (expressionName != null) { + if (path === 'translation') { + const times = origTrack.times; + const values = new Float32Array(origTrack.values.length / 3); + for (let i = 0; i < values.length; i++) { + values[i] = origTrack.values[3 * i]; + } + + const newTrack = new THREE.NumberKeyframeTrack(`${expressionName}.weight`, times as any, values as any); + + if (vrmExpressionPresetNameSet.has(expressionName)) { + result.expressionTracks.preset.set(expressionName as VRMExpressionPresetName, newTrack); + } else { + result.expressionTracks.custom.set(expressionName, newTrack); + } + } else { + throw new Error(`Invalid path "${path}"`); + } + return; + } + + // lookAt + if (node === nodeMap.lookAtIndex) { + if (path === 'rotation') { + result.lookAtTrack = origTrack; + } else { + throw new Error(`Invalid path "${path}"`); + } + } + }); + + return result; + } +} diff --git a/packages/three-vrm-animation/src/VRMAnimationLoaderPluginOptions.ts b/packages/three-vrm-animation/src/VRMAnimationLoaderPluginOptions.ts new file mode 100644 index 000000000..0129fb523 --- /dev/null +++ b/packages/three-vrm-animation/src/VRMAnimationLoaderPluginOptions.ts @@ -0,0 +1,7 @@ +export interface VRMAnimationLoaderPluginOptions { + /** + * Specify a desired frame rate in case if the animation need to be baked. + * Animation bake occurs when the lookAt target is not a direct child of the root. + */ + bakeFrameRate?: number; +} diff --git a/packages/three-vrm-animation/src/VRMLookAtQuaternionProxy.ts b/packages/three-vrm-animation/src/VRMLookAtQuaternionProxy.ts new file mode 100644 index 000000000..554bd4297 --- /dev/null +++ b/packages/three-vrm-animation/src/VRMLookAtQuaternionProxy.ts @@ -0,0 +1,39 @@ +import { VRMLookAt } from '@pixiv/three-vrm-core'; +import * as THREE from 'three'; + +const RAD2DEG = 180 / Math.PI; + +const _eulerA = /*@__PURE__*/ new THREE.Euler(); + +export class VRMLookAtQuaternionProxy extends THREE.Object3D { + public readonly vrmLookAt: VRMLookAt; + public override readonly type: string | 'VRMLookAtQuaternionProxy'; + + public constructor(lookAt: VRMLookAt) { + super(); + + this.vrmLookAt = lookAt; + + this.type = 'VRMLookAtQuaternionProxy'; + + // See: https://github.com/mrdoob/three.js/blob/r158/src/core/Object3D.js#L65 + const prevRotationOnChangeCallback = this.rotation._onChangeCallback; + this.rotation._onChange(() => { + prevRotationOnChangeCallback(); + this._applyToLookAt(); + }); + + const prevQuaternionOnChangeCallback = this.quaternion._onChangeCallback; + this.quaternion._onChange(() => { + prevQuaternionOnChangeCallback(); + this._applyToLookAt(); + }); + } + + private _applyToLookAt(): void { + _eulerA.setFromQuaternion(this.quaternion, VRMLookAt.EULER_ORDER); + + this.vrmLookAt.yaw = RAD2DEG * _eulerA.y; + this.vrmLookAt.pitch = RAD2DEG * _eulerA.x; + } +} diff --git a/packages/three-vrm-animation/src/createVRMAnimationClip.ts b/packages/three-vrm-animation/src/createVRMAnimationClip.ts new file mode 100644 index 000000000..1dfa8a8a4 --- /dev/null +++ b/packages/three-vrm-animation/src/createVRMAnimationClip.ts @@ -0,0 +1,150 @@ +import * as THREE from 'three'; +import type { + VRMCore, + VRMExpressionManager, + VRMExpressionPresetName, + VRMHumanBoneName, + VRMHumanoid, +} from '@pixiv/three-vrm-core'; +import type { VRMAnimation } from './VRMAnimation'; +import { VRMLookAtQuaternionProxy } from './VRMLookAtQuaternionProxy'; + +export function createVRMAnimationHumanoidTracks( + vrmAnimation: VRMAnimation, + humanoid: VRMHumanoid, + metaVersion: '0' | '1', +): { + translation: Map<'hips', THREE.VectorKeyframeTrack>; + rotation: Map; +} { + const translation = new Map<'hips', THREE.VectorKeyframeTrack>(); + const rotation = new Map(); + + for (const [name, origTrack] of vrmAnimation.humanoidTracks.rotation.entries()) { + const nodeName = humanoid.getNormalizedBoneNode(name)?.name; + + if (nodeName != null) { + const track = new THREE.QuaternionKeyframeTrack( + `${nodeName}.quaternion`, + origTrack.times, + origTrack.values.map((v, i) => (metaVersion === '0' && i % 2 === 0 ? -v : v)), + ); + rotation.set(name, track); + } + } + + for (const [name, origTrack] of vrmAnimation.humanoidTracks.translation.entries()) { + const nodeName = humanoid.getNormalizedBoneNode(name)?.name; + + if (nodeName != null) { + const animationY = vrmAnimation.restHipsPosition.y; + const humanoidY = humanoid.normalizedRestPose.hips!.position![1]; + const scale = humanoidY / animationY; + + const track = origTrack.clone(); + track.values = track.values.map((v, i) => (metaVersion === '0' && i % 3 !== 1 ? -v : v) * scale); + track.name = `${nodeName}.position`; + translation.set(name, track); + } + } + + return { translation, rotation }; +} + +export function createVRMAnimationExpressionTracks( + vrmAnimation: VRMAnimation, + expressionManager: VRMExpressionManager, +): { + preset: Map; + custom: Map; +} { + const preset = new Map(); + const custom = new Map(); + + for (const [name, origTrack] of vrmAnimation.expressionTracks.preset.entries()) { + const trackName = expressionManager.getExpressionTrackName(name); + + if (trackName != null) { + const track = origTrack.clone(); + track.name = trackName; + preset.set(name, track); + } + } + + for (const [name, origTrack] of vrmAnimation.expressionTracks.custom.entries()) { + const trackName = expressionManager.getExpressionTrackName(name); + + if (trackName != null) { + const track = origTrack.clone(); + track.name = trackName; + custom.set(name, track); + } + } + + return { preset, custom }; +} + +export function createVRMAnimationLookAtTrack( + vrmAnimation: VRMAnimation, + trackName: string, +): THREE.KeyframeTrack | null { + if (vrmAnimation.lookAtTrack == null) { + return null; + } + + const track = vrmAnimation.lookAtTrack.clone(); + track.name = trackName; + return track; +} + +/** + * Create an AnimationClip out of the given VRMAnimation and the VRM. + * + * @param vrmAnimation A {@link VRMAnimation}. + * @param vrm A {@link VRMCore}. + * @returns An AnimationClip + */ +export function createVRMAnimationClip(vrmAnimation: VRMAnimation, vrm: VRMCore): THREE.AnimationClip { + const tracks: THREE.KeyframeTrack[] = []; + + const humanoidTracks = createVRMAnimationHumanoidTracks(vrmAnimation, vrm.humanoid, vrm.meta.metaVersion); + tracks.push(...humanoidTracks.translation.values()); + tracks.push(...humanoidTracks.rotation.values()); + + if (vrm.expressionManager != null) { + const expressionTracks = createVRMAnimationExpressionTracks(vrmAnimation, vrm.expressionManager); + tracks.push(...expressionTracks.preset.values()); + tracks.push(...expressionTracks.custom.values()); + } + + if (vrm.lookAt != null) { + // search VRMLookAtQuaternionProxy + let proxy = vrm.scene.children.find((obj) => obj instanceof VRMLookAtQuaternionProxy); + + if (proxy == null) { + // if not found, create a new one + console.warn( + 'createVRMAnimationClip: VRMLookAtQuaternionProxy is not found. Creating a new one automatically. To suppress this warning, create a VRMLookAtQuaternionProxy manually', + ); + + proxy = new VRMLookAtQuaternionProxy(vrm.lookAt); + proxy.name = 'VRMLookAtQuaternionProxy'; + vrm.scene.add(proxy); + } else if (proxy.name == null) { + // if found but name is not set, set the name automatically + console.warn( + 'createVRMAnimationClip: VRMLookAtQuaternionProxy is found but its name is not set. Setting the name automatically. To suppress this warning, set the name manually', + ); + + proxy.name = 'VRMLookAtQuaternionProxy'; + } + + // create a track + const track = createVRMAnimationLookAtTrack(vrmAnimation, `${proxy.name}.quaternion`); + if (track != null) { + tracks.push(track); + } + } + + return new THREE.AnimationClip('Clip', vrmAnimation.duration, tracks); +} diff --git a/packages/three-vrm-animation/src/index.ts b/packages/three-vrm-animation/src/index.ts new file mode 100644 index 000000000..0ff6ad3a8 --- /dev/null +++ b/packages/three-vrm-animation/src/index.ts @@ -0,0 +1,9 @@ +export { + createVRMAnimationHumanoidTracks, + createVRMAnimationExpressionTracks, + createVRMAnimationLookAtTrack, + createVRMAnimationClip, +} from './createVRMAnimationClip'; +export { VRMAnimation } from './VRMAnimation'; +export { VRMAnimationLoaderPlugin } from './VRMAnimationLoaderPlugin'; +export { VRMLookAtQuaternionProxy } from './VRMLookAtQuaternionProxy'; diff --git a/packages/three-vrm-animation/src/utils/arrayChunk.ts b/packages/three-vrm-animation/src/utils/arrayChunk.ts new file mode 100644 index 000000000..ab5a845b9 --- /dev/null +++ b/packages/three-vrm-animation/src/utils/arrayChunk.ts @@ -0,0 +1,30 @@ +/** + * ```js + * arrayChunk( [ 1, 2, 3, 4, 5, 6 ], 2 ) + * // will be + * [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ] + * ``` + */ +export function arrayChunk(array: ArrayLike, every: number): T[][] { + const N = array.length; + + const ret: T[][] = []; + + let current: T[] = []; + let remaining = 0; + + for (let i = 0; i < N; i++) { + const el = array[i]; + + if (remaining <= 0) { + remaining = every; + current = []; + ret.push(current); + } + + current.push(el); + remaining--; + } + + return ret; +} diff --git a/packages/three-vrm-animation/tsconfig.json b/packages/three-vrm-animation/tsconfig.json new file mode 100644 index 000000000..7513033f0 --- /dev/null +++ b/packages/three-vrm-animation/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src"] +} diff --git a/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts b/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts index 07930d8ca..83aab2a6e 100644 --- a/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts +++ b/packages/three-vrm-materials-mtoon/src/MToonMaterial.ts @@ -446,7 +446,7 @@ export class MToonMaterial extends THREE.ShaderMaterial { uvAnimationRotationPhase: { value: 0.0 }, }, parameters.uniforms ?? {}, - ]) as any; + ]) as typeof MToonMaterial.prototype.uniforms; // == finally compile the shader program ======================================================= this.setValues(parameters); diff --git a/packages/types-vrmc-vrm-animation-1.0/README.md b/packages/types-vrmc-vrm-animation-1.0/README.md new file mode 100644 index 000000000..db31f3d74 --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/README.md @@ -0,0 +1,13 @@ +# @pixiv/types-vrmc-vrm-animation-1.0 + +Type definitions of `VRM_vrm_animation` 1.0 schema. + +https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_vrm_animation-1.0 + +There should not be any implementation in this package. Just type definitions of the schema. + +The extension root is named `VRMCVRMAnimation` . + +[GitHub Repository](https://github.com/pixiv/three-vrm/tree/dev/packages/types-vrmc-vrm-animation-1.0) + +[Documentation](https://pixiv.github.io/three-vrm/packages/types-vrmc-vrm-animation-1.0/docs) diff --git a/packages/types-vrmc-vrm-animation-1.0/package.json b/packages/types-vrmc-vrm-animation-1.0/package.json new file mode 100644 index 000000000..dcaa05cf7 --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/package.json @@ -0,0 +1,44 @@ +{ + "name": "@pixiv/types-vrmc-vrm-animation-1.0", + "version": "2.0.10", + "description": "Type definitions of VRMC_vrm_animation 1.0 schema", + "license": "MIT", + "author": "pixiv", + "files": [ + "/ts*/", + "/types/", + "LICENSE" + ], + "main": "", + "types": "types/index.d.ts", + "typesVersions": { + "<3.9": { + "*": [ + "ts3.4/*" + ] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/pixiv/three-vrm.git", + "directory": "packages/types-vrmc-vrm-animation-1.0" + }, + "scripts": { + "version": "yarn all", + "all": "yarn lint && yarn clean && yarn build && yarn docs", + "clean": "rimraf docs/ ts*/ types/", + "build": "tsc --declaration --declarationDir ./types --emitDeclarationOnly && downlevel-dts types ts3.4/types", + "docs": "typedoc --entryPoints ./src/index.ts --out docs", + "lint": "eslint \"src/**/*.{ts,tsx}\" && prettier \"src/**/*.{ts,tsx}\" --check", + "lint-fix": "eslint \"src/**/*.{ts,tsx}\" --fix && prettier \"src/**/*.{ts,tsx}\" --write" + }, + "lint-staged": { + "./src/**/*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] + }, + "dependencies": { + "@pixiv/types-vrmc-vrm-1.0": "2.0.3" + } +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/Expression.ts b/packages/types-vrmc-vrm-animation-1.0/src/Expression.ts new file mode 100644 index 000000000..6e5f48201 --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/Expression.ts @@ -0,0 +1,12 @@ +/** + * Represents a single expression. + */ +export interface Expression { + /** + * Represents a single glTF node mapped to this expression. + */ + node: number; + + extensions?: { [name: string]: any }; + extras?: any; +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/Expressions.ts b/packages/types-vrmc-vrm-animation-1.0/src/Expressions.ts new file mode 100644 index 000000000..5f4471906 --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/Expressions.ts @@ -0,0 +1,21 @@ +import type { Expression } from './Expression'; +import type { ExpressionPresetName } from '@pixiv/types-vrmc-vrm-1.0'; + +/** + * An object which maps expressions to nodes. + */ +export interface Expressions { + /** + * An object that contains definitions of preset expressions. + */ + preset?: { + [preset in ExpressionPresetName]?: Expression; + }; + + /** + * An object that contains definitions of custom expressions. + */ + custom?: { + [key: string]: Expression; + }; +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/Humanoid.ts b/packages/types-vrmc-vrm-animation-1.0/src/Humanoid.ts new file mode 100644 index 000000000..fe6e58a1d --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/Humanoid.ts @@ -0,0 +1,11 @@ +import { HumanoidHumanBones } from './HumanoidHumanBones'; + +/** + * An object which describes about humanoid bones. + */ +export interface Humanoid { + /** + * An object which maps humanoid bones to nodes. + */ + humanBones: HumanoidHumanBones; +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/HumanoidHumanBone.ts b/packages/types-vrmc-vrm-animation-1.0/src/HumanoidHumanBone.ts new file mode 100644 index 000000000..91f25977b --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/HumanoidHumanBone.ts @@ -0,0 +1,12 @@ +/** + * Represents a single bone of a Humanoid. + */ +export interface HumanoidHumanBone { + /** + * Represents a single glTF node mapped to this humanBone. + */ + node: number; + + extensions?: { [name: string]: any }; + extras?: any; +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/HumanoidHumanBones.ts b/packages/types-vrmc-vrm-animation-1.0/src/HumanoidHumanBones.ts new file mode 100644 index 000000000..c6ffdc0bc --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/HumanoidHumanBones.ts @@ -0,0 +1,9 @@ +import type { HumanoidHumanBone } from './HumanoidHumanBone'; +import type { HumanoidHumanBoneName } from '@pixiv/types-vrmc-vrm-1.0'; + +/** + * An object which maps humanoid bones to nodes. + */ +export type HumanoidHumanBones = { + [key in HumanoidHumanBoneName]: HumanoidHumanBone; +}; diff --git a/packages/types-vrmc-vrm-animation-1.0/src/LookAt.ts b/packages/types-vrmc-vrm-animation-1.0/src/LookAt.ts new file mode 100644 index 000000000..9fcd2ede6 --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/LookAt.ts @@ -0,0 +1,12 @@ +/** + * An object which maps a eye gaze point to a node. + */ +export interface LookAt { + /** + * Represents a single glTF node represents the eye gaze point. + */ + node: number; + + extensions?: { [name: string]: any }; + extras?: any; +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/VRMCVRMAnimation.ts b/packages/types-vrmc-vrm-animation-1.0/src/VRMCVRMAnimation.ts new file mode 100644 index 000000000..7f9f6fa9e --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/VRMCVRMAnimation.ts @@ -0,0 +1,31 @@ +import type { Expressions } from './Expressions'; +import type { Humanoid } from './Humanoid'; +import type { LookAt } from './LookAt'; + +/** + * glTF extension that defines humanoid animations. + */ +export interface VRMCVRMAnimation { + /** + * Specification version of VRMC_vrm_animation + */ + specVersion: '1.0' | '1.0-draft'; + + /** + * An object which describes about humanoid bones. + */ + humanoid: Humanoid; + + /** + * An object which maps expressions to nodes. + */ + expressions?: Expressions; + + /** + * An object which maps a eye gaze point to a node. + */ + lookAt?: LookAt; + + extensions?: { [name: string]: any }; + extras?: any; +} diff --git a/packages/types-vrmc-vrm-animation-1.0/src/index.ts b/packages/types-vrmc-vrm-animation-1.0/src/index.ts new file mode 100644 index 000000000..ff5d2fe06 --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/src/index.ts @@ -0,0 +1,7 @@ +export type { Expression } from './Expression'; +export type { Expressions } from './Expressions'; +export type { Humanoid } from './Humanoid'; +export type { HumanoidHumanBone } from './HumanoidHumanBone'; +export type { HumanoidHumanBones } from './HumanoidHumanBones'; +export type { LookAt } from './LookAt'; +export type { VRMCVRMAnimation } from './VRMCVRMAnimation'; diff --git a/packages/types-vrmc-vrm-animation-1.0/tsconfig.json b/packages/types-vrmc-vrm-animation-1.0/tsconfig.json new file mode 100644 index 000000000..5bac6173e --- /dev/null +++ b/packages/types-vrmc-vrm-animation-1.0/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "./src" + ] +} diff --git a/yarn.lock b/yarn.lock index f00324051..3fdace5f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1191,6 +1191,11 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" +"@pixiv/types-vrmc-vrm-1.0@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@pixiv/types-vrmc-vrm-1.0/-/types-vrmc-vrm-1.0-2.0.3.tgz#6f3674b955d54c0c5489bc57e0e19552d0c613e1" + integrity sha512-RMP34Bk1qLFQv/CRB1Zqvn2qMFfWQfP2Hms5QrrfoBsW9XroZdEe0zPLNbGmUPYh4F7VtBb+B7+RCuymRtpehA== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -1647,7 +1652,7 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -2259,6 +2264,22 @@ cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cli-truncate@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" @@ -2377,7 +2398,7 @@ colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== -colorette@^2.0.20: +colorette@^2.0.19, colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== @@ -2407,6 +2428,11 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^9.4.1: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -3228,6 +3254,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" + integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^3.0.1" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -3619,7 +3660,7 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -3951,6 +3992,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" + integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== + human-signals@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" @@ -5097,6 +5143,11 @@ libnpmpublish@7.1.4: sigstore "^1.4.0" ssri "^10.0.1" +lilconfig@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + lilconfig@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc" @@ -5112,6 +5163,25 @@ lines-and-columns@~2.0.3: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b" integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== +lint-staged@13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.1.2.tgz#443636a0cfd834d5518d57d228130dc04c83d6fb" + integrity sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.19" + commander "^9.4.1" + debug "^4.3.4" + execa "^6.1.0" + lilconfig "2.0.6" + listr2 "^5.0.5" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.2" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.1.3" + lint-staged@^15.1.0: version "15.2.0" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.0.tgz#3111534ca58096a3c8f70b044b6e7fe21b36f859" @@ -5140,6 +5210,20 @@ listr2@8.0.0: rfdc "^1.3.0" wrap-ansi "^9.0.0" +listr2@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-5.0.8.tgz#a9379ffeb4bd83a68931a65fb223a11510d6ba23" + integrity sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.19" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.8.0" + through "^2.3.8" + wrap-ansi "^7.0.0" + load-json-file@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" @@ -5210,6 +5294,16 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + log-update@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.0.0.tgz#0ddeb7ac6ad658c944c1de902993fce7c33f5e59" @@ -5372,7 +5466,7 @@ meshoptimizer@~0.18.1: resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8" integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw== -micromatch@4.0.5, micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@4.0.5, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -6033,6 +6127,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.12.2: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -6403,7 +6502,7 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pidtree@0.6.0: +pidtree@0.6.0, pidtree@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== @@ -6959,6 +7058,13 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" +rxjs@^7.8.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -7135,6 +7241,15 @@ slash@3.0.0, slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -7339,7 +7454,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -string-argv@0.3.2: +string-argv@0.3.2, string-argv@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== @@ -7370,7 +7485,7 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^5.0.1, string-width@^5.1.2: +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -7618,7 +7733,7 @@ through2@^4.0.0: dependencies: readable-stream "3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -8288,7 +8403,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@2.3.4: +yaml@2.3.4, yaml@^2.1.3: version "2.3.4" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==