refactor(stage-ui,stage-ui-live2d,stage-ui-three): split out stage-ui-live2d component
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "@proj-airi/stage-ui-live2d",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "Live2D scene components and stores for Project AIRI",
|
||||
"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/stage-ui-live2d"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./components": "./src/components/index.ts",
|
||||
"./components/scenes": "./src/components/scenes/index.ts",
|
||||
"./components/scenes/Live2D.vue": "./src/components/scenes/Live2D.vue",
|
||||
"./components/scenes/live2d": "./src/components/scenes/live2d/index.ts",
|
||||
"./composables/live2d": "./src/composables/live2d/index.ts",
|
||||
"./constants/emotions": "./src/constants/emotions.ts",
|
||||
"./stores": "./src/stores/index.ts",
|
||||
"./stores/live2d": "./src/stores/live2d.ts",
|
||||
"./utils/eye-motions": "./src/utils/eye-motions.ts",
|
||||
"./utils/live2d-preview": "./src/utils/live2d-preview.ts",
|
||||
"./utils/live2d-zip-loader": "./src/utils/live2d-zip-loader.ts",
|
||||
"./utils/live2d-opfs-registration": "./src/utils/live2d-opfs-registration.ts",
|
||||
"./utils/live2d-uri-encode-filenames": "./src/utils/live2d-uri-encode-filenames.ts",
|
||||
"./utils/opfs-loader": "./src/utils/opfs-loader.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lemonneko/crop-empty-pixels": "0.1.1",
|
||||
"@pixi/app": "^6.5.10",
|
||||
"@pixi/constants": "^6.5.10",
|
||||
"@pixi/core": "^6.5.10",
|
||||
"@pixi/display": "^6.5.10",
|
||||
"@pixi/extensions": "^6.5.10",
|
||||
"@pixi/interaction": "^6.5.10",
|
||||
"@pixi/loaders": "^6.5.10",
|
||||
"@pixi/math": "^6.5.10",
|
||||
"@pixi/runner": "^6.5.10",
|
||||
"@pixi/settings": "^6.5.10",
|
||||
"@pixi/sprite": "^6.5.10",
|
||||
"@pixi/ticker": "^6.5.10",
|
||||
"@pixi/utils": "^6.5.10",
|
||||
"@proj-airi/stage-shared": "workspace:^",
|
||||
"@proj-airi/ui": "workspace:^",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"culori": "^4.0.2",
|
||||
"es-toolkit": "catalog:",
|
||||
"jszip": "^3.10.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pixi-filters": "4",
|
||||
"pixi-live2d-display": "^0.4.0",
|
||||
"three": "^0.182.0",
|
||||
"vue": "^3.5.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/culori": "^4.0.1",
|
||||
"@types/three": "^0.182.0",
|
||||
"vue-tsc": "^3.1.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './scenes'
|
||||
+19
@@ -19,11 +19,24 @@ withDefaults(defineProps<{
|
||||
mouthOpenSize?: number
|
||||
focusAt?: { x: number, y: number }
|
||||
disableFocusAt?: boolean
|
||||
scale?: number
|
||||
themeColorsHue?: number
|
||||
themeColorsHueDynamic?: boolean
|
||||
live2dIdleAnimationEnabled?: boolean
|
||||
live2dAutoBlinkEnabled?: boolean
|
||||
live2dForceAutoBlinkEnabled?: boolean
|
||||
live2dShadowEnabled?: boolean
|
||||
}>(), {
|
||||
paused: false,
|
||||
focusAt: () => ({ x: 0, y: 0 }),
|
||||
mouthOpenSize: 0,
|
||||
scale: 1,
|
||||
themeColorsHue: 220.44,
|
||||
themeColorsHueDynamic: false,
|
||||
live2dIdleAnimationEnabled: true,
|
||||
live2dAutoBlinkEnabled: true,
|
||||
live2dForceAutoBlinkEnabled: false,
|
||||
live2dShadowEnabled: true,
|
||||
})
|
||||
|
||||
const componentState = defineModel<'pending' | 'loading' | 'mounted'>('state', { default: 'pending' })
|
||||
@@ -77,6 +90,12 @@ defineExpose({
|
||||
:y-offset="position.y"
|
||||
:scale="scale"
|
||||
:disable-focus-at="disableFocusAt"
|
||||
:theme-colors-hue="themeColorsHue"
|
||||
:theme-colors-hue-dynamic="themeColorsHueDynamic"
|
||||
:live2d-idle-animation-enabled="live2dIdleAnimationEnabled"
|
||||
:live2d-auto-blink-enabled="live2dAutoBlinkEnabled"
|
||||
:live2d-force-auto-blink-enabled="live2dForceAutoBlinkEnabled"
|
||||
:live2d-shadow-enabled="live2dShadowEnabled"
|
||||
/>
|
||||
</Live2DCanvas>
|
||||
</Screen>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Live2DScene } from './Live2D.vue'
|
||||
export { Live2DCanvas, Live2DModel } from './live2d'
|
||||
+18
-9
@@ -24,7 +24,6 @@ import {
|
||||
} from '../../../composables/live2d'
|
||||
import { Emotion, EmotionNeutralMotionName } from '../../../constants/emotions'
|
||||
import { useLive2d } from '../../../stores/live2d'
|
||||
import { useSettings } from '../../../stores/settings'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelSrc?: string
|
||||
@@ -40,12 +39,24 @@ const props = withDefaults(defineProps<{
|
||||
xOffset?: number | string
|
||||
yOffset?: number | string
|
||||
scale?: number
|
||||
themeColorsHue?: number
|
||||
themeColorsHueDynamic?: boolean
|
||||
live2dIdleAnimationEnabled?: boolean
|
||||
live2dAutoBlinkEnabled?: boolean
|
||||
live2dForceAutoBlinkEnabled?: boolean
|
||||
live2dShadowEnabled?: boolean
|
||||
}>(), {
|
||||
mouthOpenSize: 0,
|
||||
paused: false,
|
||||
focusAt: () => ({ x: 0, y: 0 }),
|
||||
disableFocusAt: false,
|
||||
scale: 1,
|
||||
themeColorsHue: 220.44,
|
||||
themeColorsHueDynamic: false,
|
||||
live2dIdleAnimationEnabled: true,
|
||||
live2dAutoBlinkEnabled: true,
|
||||
live2dForceAutoBlinkEnabled: false,
|
||||
live2dShadowEnabled: true,
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
@@ -136,14 +147,12 @@ const {
|
||||
modelParameters,
|
||||
} = storeToRefs(live2dStore)
|
||||
|
||||
const {
|
||||
themeColorsHue,
|
||||
themeColorsHueDynamic,
|
||||
live2dIdleAnimationEnabled,
|
||||
live2dAutoBlinkEnabled,
|
||||
live2dForceAutoBlinkEnabled,
|
||||
live2dShadowEnabled,
|
||||
} = storeToRefs(useSettings())
|
||||
const themeColorsHue = toRef(() => props.themeColorsHue)
|
||||
const themeColorsHueDynamic = toRef(() => props.themeColorsHueDynamic)
|
||||
const live2dIdleAnimationEnabled = toRef(() => props.live2dIdleAnimationEnabled)
|
||||
const live2dAutoBlinkEnabled = toRef(() => props.live2dAutoBlinkEnabled)
|
||||
const live2dForceAutoBlinkEnabled = toRef(() => props.live2dForceAutoBlinkEnabled)
|
||||
const live2dShadowEnabled = toRef(() => props.live2dShadowEnabled)
|
||||
|
||||
const localCurrentMotion = ref<{ group: string, index: number }>({ group: 'Idle', index: 0 })
|
||||
const beatSync = createBeatSyncController({
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import type { InternalModel } from 'pixi-live2d-display/cubism4'
|
||||
|
||||
import { lerp, randFloat } from 'three/src/math/MathUtils.js'
|
||||
import { MathUtils } from 'three'
|
||||
|
||||
import { randomSaccadeInterval } from '../../utils'
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useLive2DIdleEyeFocus() {
|
||||
// Function to handle idle eye saccades and focus (head) movements
|
||||
function update(model: InternalModel, now: number) {
|
||||
if (now >= nextSaccadeAfter || now < lastSaccadeAt) {
|
||||
focusTarget = [randFloat(-1, 1), randFloat(-1, 0.7)]
|
||||
focusTarget = [MathUtils.randFloat(-1, 1), MathUtils.randFloat(-1, 0.7)]
|
||||
lastSaccadeAt = now
|
||||
nextSaccadeAfter = now + (randomSaccadeInterval() / 1000)
|
||||
model.focusController.focus(focusTarget![0] * 0.5, focusTarget![1] * 0.5, false)
|
||||
@@ -26,8 +26,8 @@ export function useLive2DIdleEyeFocus() {
|
||||
model.focusController.update(now - lastSaccadeAt)
|
||||
const coreModel = model.coreModel as any
|
||||
// TODO: After emotion mapper, stage editor, eye related parameters should be take cared to be dynamical instead of hardcoding
|
||||
coreModel.setParameterValueById('ParamEyeBallX', lerp(coreModel.getParameterValueById('ParamEyeBallX'), focusTarget![0], 0.3))
|
||||
coreModel.setParameterValueById('ParamEyeBallY', lerp(coreModel.getParameterValueById('ParamEyeBallY'), focusTarget![1], 0.3))
|
||||
coreModel.setParameterValueById('ParamEyeBallX', MathUtils.lerp(coreModel.getParameterValueById('ParamEyeBallX'), focusTarget![0], 0.3))
|
||||
coreModel.setParameterValueById('ParamEyeBallY', MathUtils.lerp(coreModel.getParameterValueById('ParamEyeBallY'), focusTarget![1], 0.3))
|
||||
}
|
||||
|
||||
return { update }
|
||||
@@ -0,0 +1,56 @@
|
||||
export const EMOTION_HAPPY = '<|EMOTE_HAPPY|>'
|
||||
export const EMOTION_SAD = '<|EMOTE_SAD|>'
|
||||
export const EMOTION_ANGRY = '<|EMOTE_ANGRY|>'
|
||||
export const EMOTION_THINK = '<|EMOTE_THINK|>'
|
||||
export const EMOTION_SURPRISE = '<|EMOTE_SURPRISED|>'
|
||||
export const EMOTION_AWKWARD = '<|EMOTE_AWKWARD|>'
|
||||
export const EMOTION_QUESTION = '<|EMOTE_QUESTION|>'
|
||||
export const EMOTION_CURIOUS = '<|EMOTE_CURIOUS|>'
|
||||
|
||||
export enum Emotion {
|
||||
Idle = '<|EMOTE_NEUTRAL|>',
|
||||
Happy = '<|EMOTE_HAPPY|>',
|
||||
Sad = '<|EMOTE_SAD|>',
|
||||
Angry = '<|EMOTE_ANGRY|>',
|
||||
Think = '<|EMOTE_THINK|>',
|
||||
Surprise = '<|EMOTE_SURPRISED|>',
|
||||
Awkward = '<|EMOTE_AWKWARD|>',
|
||||
Question = '<|EMOTE_QUESTION|>',
|
||||
Curious = '<|EMOTE_CURIOUS|>',
|
||||
}
|
||||
|
||||
export const EMOTION_VALUES = Object.values(Emotion)
|
||||
|
||||
export const EmotionHappyMotionName = 'Happy'
|
||||
export const EmotionSadMotionName = 'Sad'
|
||||
export const EmotionAngryMotionName = 'Angry'
|
||||
export const EmotionAwkwardMotionName = 'Awkward'
|
||||
export const EmotionThinkMotionName = 'Think'
|
||||
export const EmotionSurpriseMotionName = 'Surprise'
|
||||
export const EmotionQuestionMotionName = 'Question'
|
||||
export const EmotionNeutralMotionName = 'Idle'
|
||||
export const EmotionCuriousMotionName = 'Curious'
|
||||
|
||||
export const EMOTION_EmotionMotionName_value = {
|
||||
[Emotion.Happy]: EmotionHappyMotionName,
|
||||
[Emotion.Sad]: EmotionSadMotionName,
|
||||
[Emotion.Angry]: EmotionAngryMotionName,
|
||||
[Emotion.Think]: EmotionThinkMotionName,
|
||||
[Emotion.Surprise]: EmotionSurpriseMotionName,
|
||||
[Emotion.Awkward]: EmotionAwkwardMotionName,
|
||||
[Emotion.Question]: EmotionQuestionMotionName,
|
||||
[Emotion.Idle]: EmotionNeutralMotionName,
|
||||
[Emotion.Curious]: EmotionCuriousMotionName,
|
||||
}
|
||||
|
||||
export const EMOTION_VRMExpressionName_value = {
|
||||
[Emotion.Happy]: 'happy',
|
||||
[Emotion.Sad]: 'sad',
|
||||
[Emotion.Angry]: 'angry',
|
||||
[Emotion.Think]: undefined,
|
||||
[Emotion.Surprise]: 'surprised',
|
||||
[Emotion.Awkward]: undefined,
|
||||
[Emotion.Question]: undefined,
|
||||
[Emotion.Idle]: undefined,
|
||||
[Emotion.Curious]: 'surprised',
|
||||
} satisfies Record<Emotion, string | undefined>
|
||||
@@ -0,0 +1,11 @@
|
||||
export { Live2DCanvas, Live2DModel } from './components/scenes/live2d'
|
||||
export { default as Live2DScene } from './components/scenes/Live2D.vue'
|
||||
export * from './composables/live2d'
|
||||
export * from './constants/emotions'
|
||||
export * from './stores'
|
||||
export { randomSaccadeInterval } from './utils/eye-motions'
|
||||
export * from './utils/live2d-opfs-registration'
|
||||
export * from './utils/live2d-preview'
|
||||
export * from './utils/live2d-uri-encode-filenames'
|
||||
export * from './utils/live2d-zip-loader'
|
||||
export * from './utils/opfs-loader'
|
||||
@@ -0,0 +1 @@
|
||||
export * from './live2d'
|
||||
@@ -0,0 +1,92 @@
|
||||
import { refManualReset, useBroadcastChannel, useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
type BroadcastChannelEvents
|
||||
= | BroadcastChannelEventShouldUpdateView
|
||||
|
||||
interface BroadcastChannelEventShouldUpdateView {
|
||||
type: 'should-update-view'
|
||||
}
|
||||
|
||||
export const defaultModelParameters = {
|
||||
angleX: 0,
|
||||
angleY: 0,
|
||||
angleZ: 0,
|
||||
leftEyeOpen: 1,
|
||||
rightEyeOpen: 1,
|
||||
leftEyeSmile: 0,
|
||||
rightEyeSmile: 0,
|
||||
leftEyebrowLR: 0,
|
||||
rightEyebrowLR: 0,
|
||||
leftEyebrowY: 0,
|
||||
rightEyebrowY: 0,
|
||||
leftEyebrowAngle: 0,
|
||||
rightEyebrowAngle: 0,
|
||||
leftEyebrowForm: 0,
|
||||
rightEyebrowForm: 0,
|
||||
mouthOpen: 0,
|
||||
mouthForm: 0,
|
||||
cheek: 0,
|
||||
bodyAngleX: 0,
|
||||
bodyAngleY: 0,
|
||||
bodyAngleZ: 0,
|
||||
breath: 0,
|
||||
}
|
||||
|
||||
export const useLive2d = defineStore('live2d', () => {
|
||||
const { post, data } = useBroadcastChannel<BroadcastChannelEvents, BroadcastChannelEvents>({ name: 'airi-stores-live2d' })
|
||||
const shouldUpdateViewHooks = ref<Array<() => void>>([])
|
||||
|
||||
const onShouldUpdateView = (hook: () => void) => {
|
||||
shouldUpdateViewHooks.value.push(hook)
|
||||
}
|
||||
|
||||
function shouldUpdateView() {
|
||||
post({ type: 'should-update-view' })
|
||||
shouldUpdateViewHooks.value.forEach(hook => hook())
|
||||
}
|
||||
|
||||
watch(data, (event) => {
|
||||
if (event.type === 'should-update-view') {
|
||||
shouldUpdateViewHooks.value.forEach(hook => hook())
|
||||
}
|
||||
})
|
||||
|
||||
const position = refManualReset<{ x: number, y: number }>(useLocalStorage('settings/live2d/position', { x: 0, y: 0 })) // position is relative to the center of the screen, units are %
|
||||
const positionInPercentageString = computed(() => ({
|
||||
x: `${position.value.x}%`,
|
||||
y: `${position.value.y}%`,
|
||||
}))
|
||||
const currentMotion = refManualReset<{ group: string, index?: number }>(() => ({ group: 'Idle', index: 0 }))
|
||||
const availableMotions = refManualReset<{ motionName: string, motionIndex: number, fileName: string }[]>(() => [])
|
||||
const motionMap = refManualReset<Record<string, string>>(useLocalStorage('settings/live2d/motion-map', {}))
|
||||
const scale = refManualReset<number>(useLocalStorage('settings/live2d/scale', 1))
|
||||
|
||||
// Live2D model parameters
|
||||
const modelParameters = refManualReset<Record<string, number>>(useLocalStorage('settings/live2d/parameters', defaultModelParameters))
|
||||
|
||||
function resetState() {
|
||||
position.reset()
|
||||
currentMotion.reset()
|
||||
availableMotions.reset()
|
||||
motionMap.reset()
|
||||
scale.reset()
|
||||
modelParameters.reset()
|
||||
shouldUpdateView()
|
||||
}
|
||||
|
||||
return {
|
||||
position,
|
||||
positionInPercentageString,
|
||||
currentMotion,
|
||||
availableMotions,
|
||||
motionMap,
|
||||
scale,
|
||||
modelParameters,
|
||||
|
||||
onShouldUpdateView,
|
||||
shouldUpdateView,
|
||||
resetState,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
export { randomSaccadeInterval } from './eye-motions'
|
||||
|
||||
export * from './live2d-opfs-registration'
|
||||
export * from './live2d-preview'
|
||||
export * from './live2d-uri-encode-filenames'
|
||||
export * from './live2d-zip-loader'
|
||||
export * from './opfs-loader'
|
||||
@@ -0,0 +1,90 @@
|
||||
import cropImg from '@lemonneko/crop-empty-pixels'
|
||||
|
||||
import { Application } from '@pixi/app'
|
||||
import { extensions } from '@pixi/extensions'
|
||||
import { Ticker, TickerPlugin } from '@pixi/ticker'
|
||||
import { Live2DFactory, Live2DModel } from 'pixi-live2d-display/cubism4'
|
||||
|
||||
/**
|
||||
* Render a Live2D zip/file to an offscreen canvas and return a padded preview data URL.
|
||||
*/
|
||||
export async function loadLive2DModelPreview(file: File) {
|
||||
Live2DModel.registerTicker(Ticker)
|
||||
extensions.add(TickerPlugin)
|
||||
|
||||
const previewWidth = 1440
|
||||
const previewHeight = 2560
|
||||
const previewResolution = 2
|
||||
|
||||
const offscreenCanvas = document.createElement('canvas')
|
||||
offscreenCanvas.width = previewWidth * previewResolution
|
||||
offscreenCanvas.height = previewHeight * previewResolution
|
||||
offscreenCanvas.style.position = 'absolute'
|
||||
offscreenCanvas.style.top = '0'
|
||||
offscreenCanvas.style.left = '0'
|
||||
offscreenCanvas.style.objectFit = 'cover'
|
||||
offscreenCanvas.style.display = 'block'
|
||||
offscreenCanvas.style.zIndex = '10000000000'
|
||||
offscreenCanvas.style.opacity = '0'
|
||||
document.body.appendChild(offscreenCanvas)
|
||||
|
||||
const app = new Application({
|
||||
view: offscreenCanvas,
|
||||
width: offscreenCanvas.width,
|
||||
height: offscreenCanvas.height,
|
||||
// Ensure the drawing buffer persists so toDataURL() can read pixels
|
||||
preserveDrawingBuffer: true,
|
||||
backgroundAlpha: 0,
|
||||
autoDensity: false,
|
||||
resolution: 1,
|
||||
autoStart: false,
|
||||
})
|
||||
app.stage.scale.set(previewResolution)
|
||||
app.ticker.stop()
|
||||
|
||||
const modelInstance = new Live2DModel()
|
||||
const objUrl = URL.createObjectURL(file)
|
||||
const res = await fetch(objUrl)
|
||||
const blob = await res.blob()
|
||||
|
||||
const cleanup = () => {
|
||||
app.destroy()
|
||||
if (offscreenCanvas.isConnected)
|
||||
document.body.removeChild(offscreenCanvas)
|
||||
URL.revokeObjectURL(objUrl)
|
||||
}
|
||||
|
||||
try {
|
||||
await Live2DFactory.setupLive2DModel(modelInstance, [new File([blob], file.name)], { autoInteract: false })
|
||||
app.stage.addChild(modelInstance)
|
||||
|
||||
modelInstance.x = 275
|
||||
modelInstance.y = 450
|
||||
modelInstance.width = previewWidth
|
||||
modelInstance.height = previewHeight
|
||||
modelInstance.scale.set(0.1, 0.1)
|
||||
modelInstance.anchor.set(0.5, 0.5)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
app.renderer.render(app.stage)
|
||||
|
||||
const croppedCanvas = cropImg(offscreenCanvas)
|
||||
|
||||
// padding to 12:16
|
||||
const paddingCanvas = document.createElement('canvas')
|
||||
paddingCanvas.width = croppedCanvas.width > croppedCanvas.height / 16 * 12 ? croppedCanvas.width : croppedCanvas.height / 16 * 12
|
||||
paddingCanvas.height = paddingCanvas.width / 12 * 16
|
||||
const paddingCanvasCtx = paddingCanvas.getContext('2d')!
|
||||
|
||||
paddingCanvasCtx.drawImage(croppedCanvas, (paddingCanvas.width - croppedCanvas.width) / 2, (paddingCanvas.height - croppedCanvas.height) / 2, croppedCanvas.width, croppedCanvas.height)
|
||||
const paddingDataUrl = paddingCanvas.toDataURL()
|
||||
|
||||
cleanup()
|
||||
|
||||
return paddingDataUrl
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ESNext",
|
||||
"DOM.AsyncIterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
"exports": {
|
||||
"./assets/vrm": "./src/assets/vrm/index.ts",
|
||||
"./composables/vrm": "./src/composables/vrm/index.ts",
|
||||
"./utils/vrm-preview": "./src/utils/vrm-preview.ts",
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default as ThreeScene } from './components/ThreeScene.vue'
|
||||
export { useModelStore } from './stores/model-store'
|
||||
export * from './utils/vrm-preview'
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { VRM } from '@pixiv/three-vrm'
|
||||
|
||||
import { AmbientLight, AnimationMixer, DirectionalLight, PerspectiveCamera, Scene, WebGLRenderer } from 'three'
|
||||
|
||||
import { animations } from '../assets/vrm'
|
||||
import { clipFromVRMAnimation, loadVrm, loadVRMAnimation, reAnchorRootPositionTrack } from '../composables/vrm'
|
||||
|
||||
/**
|
||||
* Render a VRM file to an offscreen canvas and return a preview data URL.
|
||||
*/
|
||||
export async function loadVrmModelPreview(file: File) {
|
||||
const offscreenCanvas = document.createElement('canvas')
|
||||
offscreenCanvas.width = 1440
|
||||
offscreenCanvas.height = 2560
|
||||
offscreenCanvas.style.position = 'absolute'
|
||||
offscreenCanvas.style.top = '0'
|
||||
offscreenCanvas.style.left = '0'
|
||||
offscreenCanvas.style.objectFit = 'cover'
|
||||
offscreenCanvas.style.display = 'block'
|
||||
offscreenCanvas.style.zIndex = '10000000000'
|
||||
offscreenCanvas.style.opacity = '0'
|
||||
document.body.appendChild(offscreenCanvas)
|
||||
|
||||
const renderer = new WebGLRenderer({
|
||||
canvas: offscreenCanvas,
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: true,
|
||||
})
|
||||
renderer.setSize(offscreenCanvas.width, offscreenCanvas.height, false)
|
||||
renderer.setPixelRatio(1)
|
||||
|
||||
const scene = new Scene()
|
||||
const camera = new PerspectiveCamera(40, offscreenCanvas.width / offscreenCanvas.height, 0.01, 1000)
|
||||
const ambientLight = new AmbientLight(0xFFFFFF, 0.8)
|
||||
const directionalLight = new DirectionalLight(0xFFFFFF, 0.8)
|
||||
directionalLight.position.set(1, 1, 1)
|
||||
scene.add(ambientLight, directionalLight)
|
||||
|
||||
const objUrl = URL.createObjectURL(file)
|
||||
let vrmInstance: VRM | undefined
|
||||
|
||||
try {
|
||||
const vrmData = await loadVrm(objUrl, { scene, lookAt: true })
|
||||
if (!vrmData)
|
||||
return
|
||||
|
||||
vrmInstance = vrmData._vrm
|
||||
const { modelCenter, initialCameraOffset } = vrmData
|
||||
|
||||
camera.position.copy(modelCenter).add(initialCameraOffset)
|
||||
camera.lookAt(modelCenter)
|
||||
camera.updateProjectionMatrix()
|
||||
|
||||
try {
|
||||
const animation = await loadVRMAnimation(animations.idleLoop.toString())
|
||||
const clip = await clipFromVRMAnimation(vrmData._vrm, animation)
|
||||
if (clip) {
|
||||
reAnchorRootPositionTrack(clip, vrmData._vrm)
|
||||
const mixer = new AnimationMixer(vrmData._vrm.scene)
|
||||
const action = mixer.clipAction(clip)
|
||||
action.play()
|
||||
mixer.update(0.1)
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.warn('Failed to load VRM animation for preview:', err)
|
||||
}
|
||||
|
||||
renderer.render(scene, camera)
|
||||
|
||||
const dataUrl = offscreenCanvas.toDataURL()
|
||||
return dataUrl
|
||||
}
|
||||
finally {
|
||||
renderer.dispose()
|
||||
if (vrmInstance) {
|
||||
vrmInstance.scene.traverse((child) => {
|
||||
const node = child as any
|
||||
if (node.geometry?.dispose)
|
||||
node.geometry.dispose()
|
||||
|
||||
if (node.material) {
|
||||
const materials = Array.isArray(node.material) ? node.material : [node.material]
|
||||
for (const mat of materials) {
|
||||
if (mat?.map?.dispose)
|
||||
mat.map.dispose()
|
||||
mat?.dispose?.()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
URL.revokeObjectURL(objUrl)
|
||||
if (offscreenCanvas.isConnected)
|
||||
document.body.removeChild(offscreenCanvas)
|
||||
}
|
||||
}
|
||||
@@ -51,24 +51,7 @@
|
||||
"@date-fns/utc": "^2.1.1",
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
"@huggingface/transformers": "^3.8.1",
|
||||
"@lemonneko/crop-empty-pixels": "0.1.1",
|
||||
"@moeru/eventa": "^1.0.0-alpha.10",
|
||||
"@pixi/app": "^6.5.10",
|
||||
"@pixi/constants": "^6.5.10",
|
||||
"@pixi/core": "^6.5.10",
|
||||
"@pixi/display": "^6.5.10",
|
||||
"@pixi/extensions": "^6.5.10",
|
||||
"@pixi/interaction": "^6.5.10",
|
||||
"@pixi/loaders": "^6.5.10",
|
||||
"@pixi/math": "^6.5.10",
|
||||
"@pixi/runner": "^6.5.10",
|
||||
"@pixi/settings": "^6.5.10",
|
||||
"@pixi/sprite": "^6.5.10",
|
||||
"@pixi/ticker": "^6.5.10",
|
||||
"@pixi/utils": "6",
|
||||
"@pixiv/three-vrm": "^3.4.4",
|
||||
"@pixiv/three-vrm-animation": "^3.4.4",
|
||||
"@pixiv/three-vrm-core": "^3.4.4",
|
||||
"@proj-airi/audio": "workspace:^",
|
||||
"@proj-airi/ccc": "workspace:^",
|
||||
"@proj-airi/chromatic": "^1.0.2",
|
||||
@@ -79,6 +62,7 @@
|
||||
"@proj-airi/model-driver-lipsync": "workspace:^",
|
||||
"@proj-airi/server-sdk": "workspace:^",
|
||||
"@proj-airi/stage-shared": "workspace:^",
|
||||
"@proj-airi/stage-ui-live2d": "workspace:^",
|
||||
"@proj-airi/stage-ui-three": "workspace:^",
|
||||
"@proj-airi/tauri-plugin-mcp": "workspace:^",
|
||||
"@proj-airi/ui": "workspace:^",
|
||||
@@ -110,17 +94,13 @@
|
||||
"gpuu": "^1.0.6",
|
||||
"hono": "catalog:",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jszip": "^3.10.1",
|
||||
"localforage": "^1.10.0",
|
||||
"mediabunny": "^1.27.2",
|
||||
"nanoid": "^5.1.6",
|
||||
"node-vibrant": "^4.0.3",
|
||||
"ofetch": "^1.5.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pixi-filters": "4",
|
||||
"pixi-live2d-display": "^0.4.0",
|
||||
"posthog-js": "catalog:",
|
||||
"postprocessing": "^6.38.2",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"rehype-parse": "^9.0.1",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
@@ -130,7 +110,6 @@
|
||||
"remark-rehype": "^11.1.2",
|
||||
"shiki": "^3.20.0",
|
||||
"splitpanes": "catalog:",
|
||||
"three": "^0.182.0",
|
||||
"unified": "^11.0.5",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
@@ -174,7 +153,6 @@
|
||||
"@types/culori": "^4.0.1",
|
||||
"@types/hast": "catalog:",
|
||||
"@types/splitpanes": "catalog:",
|
||||
"@types/three": "^0.182.0",
|
||||
"@types/unist": "catalog:",
|
||||
"@unocss/reset": "^66.5.11",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { DisplayModel } from '../../../../stores/display-models'
|
||||
|
||||
import { Live2DScene, useLive2d } from '@proj-airi/stage-ui-live2d'
|
||||
import { ThreeScene, useModelStore } from '@proj-airi/stage-ui-three'
|
||||
import { Button, Callout } from '@proj-airi/ui'
|
||||
import { useMouse } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import Live2DScene from '../../../scenes/Live2D.vue'
|
||||
import Live2D from './live2d.vue'
|
||||
import VRM from './vrm.vue'
|
||||
|
||||
import { DisplayModelFormat } from '../../../../stores/display-models'
|
||||
import { useLive2d } from '../../../../stores/live2d'
|
||||
import { useSettings } from '../../../../stores/settings'
|
||||
import { ModelSelectorDialog } from '../../dialogs/model-selector'
|
||||
|
||||
@@ -32,7 +31,18 @@ const selectedModel = ref<DisplayModel | undefined>()
|
||||
|
||||
const positionCursor = useMouse()
|
||||
const settingsStore = useSettings()
|
||||
const { live2dDisableFocus, stageModelSelectedUrl, stageModelSelected, stageModelRenderer } = storeToRefs(settingsStore)
|
||||
const {
|
||||
live2dDisableFocus,
|
||||
stageModelSelectedUrl,
|
||||
stageModelSelected,
|
||||
stageModelRenderer,
|
||||
themeColorsHue,
|
||||
themeColorsHueDynamic,
|
||||
live2dIdleAnimationEnabled,
|
||||
live2dAutoBlinkEnabled,
|
||||
live2dForceAutoBlinkEnabled,
|
||||
live2dShadowEnabled,
|
||||
} = storeToRefs(settingsStore)
|
||||
|
||||
watch(selectedModel, async () => {
|
||||
stageModelSelected.value = selectedModel.value?.id
|
||||
@@ -86,6 +96,12 @@ watch(selectedModel, async () => {
|
||||
:model-src="stageModelSelectedUrl"
|
||||
:model-id="stageModelSelected"
|
||||
:disable-focus-at="live2dDisableFocus"
|
||||
:theme-colors-hue="themeColorsHue"
|
||||
:theme-colors-hue-dynamic="themeColorsHueDynamic"
|
||||
:live2d-idle-animation-enabled="live2dIdleAnimationEnabled"
|
||||
:live2d-auto-blink-enabled="live2dAutoBlinkEnabled"
|
||||
:live2d-force-auto-blink-enabled="live2dForceAutoBlinkEnabled"
|
||||
:live2d-shadow-enabled="live2dShadowEnabled"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { defaultModelParameters, useLive2d } from '@proj-airi/stage-ui-live2d'
|
||||
import { Button, Checkbox, FieldRange } from '@proj-airi/ui'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { defaultModelParameters, useLive2d } from '../../../../stores/live2d'
|
||||
import { useSettings } from '../../../../stores/settings'
|
||||
import { Section } from '../../../layouts'
|
||||
import { ColorPalette } from '../../../widgets'
|
||||
|
||||
@@ -13,6 +13,7 @@ import { drizzle } from '@proj-airi/drizzle-duckdb-wasm'
|
||||
import { getImportUrlBundles } from '@proj-airi/drizzle-duckdb-wasm/bundles/import-url-browser'
|
||||
import { createLive2DLipSync } from '@proj-airi/model-driver-lipsync'
|
||||
import { wlipsyncProfile } from '@proj-airi/model-driver-lipsync/shared/wlipsync'
|
||||
import { Live2DScene, useLive2d } from '@proj-airi/stage-ui-live2d'
|
||||
import { ThreeScene, useModelStore } from '@proj-airi/stage-ui-three'
|
||||
import { animations } from '@proj-airi/stage-ui-three/assets/vrm'
|
||||
import { useBroadcastChannel } from '@vueuse/core'
|
||||
@@ -23,14 +24,11 @@ import { generateSpeech } from '@xsai/generate-speech'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import Live2DScene from './Live2D.vue'
|
||||
|
||||
import { useDelayMessageQueue, useEmotionsMessageQueue, usePipelineCharacterSpeechPlaybackQueueStore, usePipelineWorkflowTextSegmentationStore } from '../../composables/queues'
|
||||
import { llmInferenceEndToken } from '../../constants'
|
||||
import { EMOTION_EmotionMotionName_value, EMOTION_VRMExpressionName_value, EmotionThinkMotionName } from '../../constants/emotions'
|
||||
import { useAudioContext, useSpeakingStore } from '../../stores/audio'
|
||||
import { useChatStore } from '../../stores/chat'
|
||||
import { useLive2d } from '../../stores/live2d'
|
||||
import { useSpeechStore } from '../../stores/modules/speech'
|
||||
import { useProvidersStore } from '../../stores/providers'
|
||||
import { useSettings } from '../../stores/settings'
|
||||
@@ -75,7 +73,19 @@ const { connectAudioContext, connectAudioAnalyser, connectLipSyncNode, clearAll,
|
||||
const { currentAudioSource, playbackQueue } = storeToRefs(characterSpeechPlaybackQueue)
|
||||
|
||||
const settingsStore = useSettings()
|
||||
const { stageModelRenderer, stageViewControlsEnabled, live2dDisableFocus, stageModelSelectedUrl, stageModelSelected } = storeToRefs(settingsStore)
|
||||
const {
|
||||
stageModelRenderer,
|
||||
stageViewControlsEnabled,
|
||||
live2dDisableFocus,
|
||||
stageModelSelectedUrl,
|
||||
stageModelSelected,
|
||||
themeColorsHue,
|
||||
themeColorsHueDynamic,
|
||||
live2dIdleAnimationEnabled,
|
||||
live2dAutoBlinkEnabled,
|
||||
live2dForceAutoBlinkEnabled,
|
||||
live2dShadowEnabled,
|
||||
} = storeToRefs(settingsStore)
|
||||
const { mouthOpenSize } = storeToRefs(useSpeakingStore())
|
||||
const { audioContext } = useAudioContext()
|
||||
connectAudioContext(audioContext)
|
||||
@@ -378,6 +388,12 @@ onPlaybackStarted(({ text }) => {
|
||||
:y-offset="yOffset"
|
||||
:scale="scale"
|
||||
:disable-focus-at="live2dDisableFocus"
|
||||
:theme-colors-hue="themeColorsHue"
|
||||
:theme-colors-hue-dynamic="themeColorsHueDynamic"
|
||||
:live2d-idle-animation-enabled="live2dIdleAnimationEnabled"
|
||||
:live2d-auto-blink-enabled="live2dAutoBlinkEnabled"
|
||||
:live2d-force-auto-blink-enabled="live2dForceAutoBlinkEnabled"
|
||||
:live2d-shadow-enabled="live2dShadowEnabled"
|
||||
/>
|
||||
<ThreeScene
|
||||
v-if="stageModelRenderer === 'vrm' && showStage"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import MarkdownRenderer from '../markdown/MarkdownRenderer.vue'
|
||||
import MarkdownRenderer from '../markdown/markdown-renderer.vue'
|
||||
|
||||
import { useChatStore } from '../../stores/chat'
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export { Live2DCanvas, Live2DModel } from './live2d'
|
||||
|
||||
export { default as SceneLive2D } from './Live2D.vue'
|
||||
export { default as WidgetStage } from './Stage.vue'
|
||||
export {
|
||||
Live2DCanvas,
|
||||
Live2DModel,
|
||||
Live2DScene as SceneLive2D,
|
||||
} from '@proj-airi/stage-ui-live2d/components/scenes'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isStageTamagotchi } from '@proj-airi/stage-shared'
|
||||
|
||||
import { useChatStore } from '../stores/chat'
|
||||
import { useDisplayModelsStore } from '../stores/display-models'
|
||||
import { useLive2d } from '../stores/live2d'
|
||||
import { useLive2d } from '@proj-airi/stage-ui-live2d'
|
||||
import { useMcpStore } from '../stores/mcp'
|
||||
import { useAiriCardStore } from '../stores/modules/airi-card'
|
||||
import { useConsciousnessStore } from '../stores/modules/consciousness'
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import type { VRM } from '@pixiv/three-vrm'
|
||||
|
||||
import cropImg from '@lemonneko/crop-empty-pixels'
|
||||
import localforage from 'localforage'
|
||||
|
||||
import { Application } from '@pixi/app'
|
||||
import { extensions } from '@pixi/extensions'
|
||||
import { Ticker, TickerPlugin } from '@pixi/ticker'
|
||||
import { animations } from '@proj-airi/stage-ui-three/assets/vrm'
|
||||
import { clipFromVRMAnimation, loadVrm, loadVRMAnimation, reAnchorRootPositionTrack } from '@proj-airi/stage-ui-three/composables/vrm'
|
||||
import { loadLive2DModelPreview as generateLive2DPreview } from '@proj-airi/stage-ui-live2d/utils/live2d-preview'
|
||||
import { loadVrmModelPreview as generateVrmPreview } from '@proj-airi/stage-ui-three/utils/vrm-preview'
|
||||
import { until } from '@vueuse/core'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { defineStore } from 'pinia'
|
||||
import { Live2DFactory, Live2DModel } from 'pixi-live2d-display/cubism4'
|
||||
import { AmbientLight, AnimationMixer, DirectionalLight, PerspectiveCamera, Scene, WebGLRenderer } from 'three'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import '../utils/live2d-zip-loader'
|
||||
import '../utils/live2d-opfs-registration'
|
||||
import '@proj-airi/stage-ui-live2d/utils/live2d-zip-loader'
|
||||
import '@proj-airi/stage-ui-live2d/utils/live2d-opfs-registration'
|
||||
|
||||
export enum DisplayModelFormat {
|
||||
Live2dZip = 'live2d-zip',
|
||||
@@ -103,186 +95,10 @@ export const useDisplayModelsStore = defineStore('display-models', () => {
|
||||
return displayModelsPresets.find(model => model.id === id)
|
||||
}
|
||||
|
||||
async function loadLive2DModelPreview(file: File) {
|
||||
Live2DModel.registerTicker(Ticker)
|
||||
extensions.add(TickerPlugin)
|
||||
|
||||
const previewWidth = 1440
|
||||
const previewHeight = 2560
|
||||
const previewResolution = 2
|
||||
|
||||
const offscreenCanvas = document.createElement('canvas')
|
||||
offscreenCanvas.width = previewWidth * previewResolution
|
||||
offscreenCanvas.height = previewHeight * previewResolution
|
||||
offscreenCanvas.style.position = 'absolute'
|
||||
offscreenCanvas.style.top = '0'
|
||||
offscreenCanvas.style.left = '0'
|
||||
offscreenCanvas.style.objectFit = 'cover'
|
||||
offscreenCanvas.style.display = 'block'
|
||||
offscreenCanvas.style.zIndex = '10000000000'
|
||||
offscreenCanvas.style.opacity = '0'
|
||||
document.body.appendChild(offscreenCanvas)
|
||||
|
||||
const app = new Application({
|
||||
view: offscreenCanvas,
|
||||
width: offscreenCanvas.width,
|
||||
height: offscreenCanvas.height,
|
||||
// Ensure the drawing buffer persists so toDataURL() can read pixels
|
||||
preserveDrawingBuffer: true,
|
||||
backgroundAlpha: 0,
|
||||
autoDensity: false,
|
||||
resolution: 1,
|
||||
autoStart: false,
|
||||
})
|
||||
app.stage.scale.set(previewResolution)
|
||||
app.ticker.stop()
|
||||
|
||||
const modelInstance = new Live2DModel()
|
||||
const objUrl = URL.createObjectURL(file)
|
||||
const res = await fetch(objUrl)
|
||||
const blob = await res.blob()
|
||||
|
||||
const cleanup = () => {
|
||||
app.destroy()
|
||||
if (offscreenCanvas.isConnected)
|
||||
document.body.removeChild(offscreenCanvas)
|
||||
URL.revokeObjectURL(objUrl)
|
||||
}
|
||||
|
||||
try {
|
||||
await Live2DFactory.setupLive2DModel(modelInstance, [new File([blob], file.name)], { autoInteract: false })
|
||||
app.stage.addChild(modelInstance)
|
||||
|
||||
// transforms
|
||||
modelInstance.x = 275
|
||||
modelInstance.y = 450
|
||||
modelInstance.width = previewWidth
|
||||
modelInstance.height = previewHeight
|
||||
modelInstance.scale.set(0.1, 0.1)
|
||||
modelInstance.anchor.set(0.5, 0.5)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
// Force a render to ensure the latest frame is in the drawing buffer
|
||||
app.renderer.render(app.stage)
|
||||
|
||||
const croppedCanvas = cropImg(offscreenCanvas)
|
||||
|
||||
// padding to 12:16
|
||||
const paddingCanvas = document.createElement('canvas')
|
||||
paddingCanvas.width = croppedCanvas.width > croppedCanvas.height / 16 * 12 ? croppedCanvas.width : croppedCanvas.height / 16 * 12
|
||||
paddingCanvas.height = paddingCanvas.width / 12 * 16
|
||||
const paddingCanvasCtx = paddingCanvas.getContext('2d')!
|
||||
|
||||
paddingCanvasCtx.drawImage(croppedCanvas, (paddingCanvas.width - croppedCanvas.width) / 2, (paddingCanvas.height - croppedCanvas.height) / 2, croppedCanvas.width, croppedCanvas.height)
|
||||
const paddingDataUrl = paddingCanvas.toDataURL()
|
||||
|
||||
cleanup()
|
||||
|
||||
// return dataUrl
|
||||
return paddingDataUrl
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
const loadLive2DModelPreview = (file: File) => generateLive2DPreview(file)
|
||||
|
||||
async function loadVrmModelPreview(file: File) {
|
||||
const offscreenCanvas = document.createElement('canvas')
|
||||
offscreenCanvas.width = 1440
|
||||
offscreenCanvas.height = 2560
|
||||
offscreenCanvas.style.position = 'absolute'
|
||||
offscreenCanvas.style.top = '0'
|
||||
offscreenCanvas.style.left = '0'
|
||||
offscreenCanvas.style.objectFit = 'cover'
|
||||
offscreenCanvas.style.display = 'block'
|
||||
offscreenCanvas.style.zIndex = '10000000000'
|
||||
offscreenCanvas.style.opacity = '0'
|
||||
document.body.appendChild(offscreenCanvas)
|
||||
|
||||
const renderer = new WebGLRenderer({
|
||||
canvas: offscreenCanvas,
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: true,
|
||||
})
|
||||
renderer.setSize(offscreenCanvas.width, offscreenCanvas.height, false)
|
||||
renderer.setPixelRatio(1)
|
||||
|
||||
const scene = new Scene()
|
||||
const camera = new PerspectiveCamera(40, offscreenCanvas.width / offscreenCanvas.height, 0.01, 1000)
|
||||
const ambientLight = new AmbientLight(0xFFFFFF, 0.8)
|
||||
const directionalLight = new DirectionalLight(0xFFFFFF, 0.8)
|
||||
directionalLight.position.set(1, 1, 1)
|
||||
scene.add(ambientLight, directionalLight)
|
||||
|
||||
const objUrl = URL.createObjectURL(file)
|
||||
let vrmInstance: VRM | undefined
|
||||
|
||||
try {
|
||||
const vrmData = await loadVrm(objUrl, { scene, lookAt: true })
|
||||
if (!vrmData) {
|
||||
return
|
||||
}
|
||||
|
||||
vrmInstance = vrmData._vrm
|
||||
const { modelCenter, initialCameraOffset } = vrmData
|
||||
|
||||
camera.position.copy(modelCenter).add(initialCameraOffset)
|
||||
camera.lookAt(modelCenter)
|
||||
camera.updateProjectionMatrix()
|
||||
|
||||
try {
|
||||
const animation = await loadVRMAnimation(animations.idleLoop.toString())
|
||||
const clip = await clipFromVRMAnimation(vrmData._vrm, animation)
|
||||
if (clip) {
|
||||
reAnchorRootPositionTrack(clip, vrmData._vrm)
|
||||
const mixer = new AnimationMixer(vrmData._vrm.scene)
|
||||
mixer.clipAction(clip).play()
|
||||
mixer.update(1 / 60)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Failed to load VRM idle animation for preview.', error)
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const start = performance.now()
|
||||
const step = (time: number) => {
|
||||
if (time - start >= 2000) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
requestAnimationFrame(step)
|
||||
}
|
||||
requestAnimationFrame(step)
|
||||
})
|
||||
renderer.render(scene, camera)
|
||||
|
||||
const croppedCanvas = cropImg(offscreenCanvas)
|
||||
|
||||
// padding to 12:16
|
||||
const paddingCanvas = document.createElement('canvas')
|
||||
paddingCanvas.width = croppedCanvas.width > croppedCanvas.height / 16 * 12 ? croppedCanvas.width : croppedCanvas.height / 16 * 12
|
||||
paddingCanvas.height = paddingCanvas.width / 12 * 16
|
||||
const paddingCanvasCtx = paddingCanvas.getContext('2d')!
|
||||
|
||||
paddingCanvasCtx.drawImage(croppedCanvas, (paddingCanvas.width - croppedCanvas.width) / 2, (paddingCanvas.height - croppedCanvas.height) / 2, croppedCanvas.width, croppedCanvas.height)
|
||||
return paddingCanvas.toDataURL()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
finally {
|
||||
if (vrmInstance && 'dispose' in vrmInstance) {
|
||||
(vrmInstance as { dispose: () => void }).dispose()
|
||||
}
|
||||
|
||||
renderer.dispose()
|
||||
document.body.removeChild(offscreenCanvas)
|
||||
URL.revokeObjectURL(objUrl)
|
||||
}
|
||||
return generateVrmPreview(file)
|
||||
}
|
||||
|
||||
async function addDisplayModel(format: DisplayModelFormat, file: File) {
|
||||
|
||||
@@ -1,94 +1 @@
|
||||
import { useBroadcastChannel } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { createResettableLocalStorage, createResettableRef } from '../utils/resettable'
|
||||
|
||||
type BroadcastChannelEvents
|
||||
= | BroadcastChannelEventShouldUpdateView
|
||||
|
||||
interface BroadcastChannelEventShouldUpdateView {
|
||||
type: 'should-update-view'
|
||||
}
|
||||
|
||||
export const defaultModelParameters = {
|
||||
angleX: 0,
|
||||
angleY: 0,
|
||||
angleZ: 0,
|
||||
leftEyeOpen: 1,
|
||||
rightEyeOpen: 1,
|
||||
leftEyeSmile: 0,
|
||||
rightEyeSmile: 0,
|
||||
leftEyebrowLR: 0,
|
||||
rightEyebrowLR: 0,
|
||||
leftEyebrowY: 0,
|
||||
rightEyebrowY: 0,
|
||||
leftEyebrowAngle: 0,
|
||||
rightEyebrowAngle: 0,
|
||||
leftEyebrowForm: 0,
|
||||
rightEyebrowForm: 0,
|
||||
mouthOpen: 0,
|
||||
mouthForm: 0,
|
||||
cheek: 0,
|
||||
bodyAngleX: 0,
|
||||
bodyAngleY: 0,
|
||||
bodyAngleZ: 0,
|
||||
breath: 0,
|
||||
}
|
||||
|
||||
export const useLive2d = defineStore('live2d', () => {
|
||||
const { post, data } = useBroadcastChannel<BroadcastChannelEvents, BroadcastChannelEvents>({ name: 'airi-stores-live2d' })
|
||||
const shouldUpdateViewHooks = ref<Array<() => void>>([])
|
||||
|
||||
const onShouldUpdateView = (hook: () => void) => {
|
||||
shouldUpdateViewHooks.value.push(hook)
|
||||
}
|
||||
|
||||
function shouldUpdateView() {
|
||||
post({ type: 'should-update-view' })
|
||||
shouldUpdateViewHooks.value.forEach(hook => hook())
|
||||
}
|
||||
|
||||
watch(data, (event) => {
|
||||
if (event.type === 'should-update-view') {
|
||||
shouldUpdateViewHooks.value.forEach(hook => hook())
|
||||
}
|
||||
})
|
||||
|
||||
const [position, resetPosition] = createResettableLocalStorage('settings/live2d/position', { x: 0, y: 0 }) // position is relative to the center of the screen, units are %
|
||||
const positionInPercentageString = computed(() => ({
|
||||
x: `${position.value.x}%`,
|
||||
y: `${position.value.y}%`,
|
||||
}))
|
||||
const [currentMotion, resetCurrentMotion] = createResettableRef<{ group: string, index?: number }>({ group: 'Idle', index: 0 })
|
||||
const [availableMotions, resetAvailableMotions] = createResettableRef<{ motionName: string, motionIndex: number, fileName: string }[]>([])
|
||||
const [motionMap, resetMotionMap] = createResettableLocalStorage<Record<string, string>>('settings/live2d/motion-map', {})
|
||||
const [scale, resetScale] = createResettableLocalStorage('settings/live2d/scale', 1)
|
||||
|
||||
// Live2D model parameters
|
||||
const [modelParameters, resetModelParameters] = createResettableLocalStorage('settings/live2d/parameters', defaultModelParameters)
|
||||
|
||||
function resetState() {
|
||||
resetPosition()
|
||||
resetCurrentMotion()
|
||||
resetAvailableMotions()
|
||||
resetMotionMap()
|
||||
resetScale()
|
||||
resetModelParameters()
|
||||
shouldUpdateView()
|
||||
}
|
||||
|
||||
return {
|
||||
position,
|
||||
positionInPercentageString,
|
||||
currentMotion,
|
||||
availableMotions,
|
||||
motionMap,
|
||||
scale,
|
||||
modelParameters,
|
||||
|
||||
onShouldUpdateView,
|
||||
shouldUpdateView,
|
||||
resetState,
|
||||
}
|
||||
})
|
||||
export * from '@proj-airi/stage-ui-live2d/stores/live2d'
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { randomSaccadeInterval } from './eye-motions'
|
||||
export { randomSaccadeInterval } from '@proj-airi/stage-ui-live2d/utils/eye-motions'
|
||||
|
||||
Generated
+100
-72
@@ -2133,60 +2133,9 @@ importers:
|
||||
'@huggingface/transformers':
|
||||
specifier: ^3.8.1
|
||||
version: 3.8.1
|
||||
'@lemonneko/crop-empty-pixels':
|
||||
specifier: 0.1.1
|
||||
version: 0.1.1
|
||||
'@moeru/eventa':
|
||||
specifier: ^1.0.0-alpha.10
|
||||
version: 1.0.0-alpha.10(electron@39.2.7)(h3@2.0.1-rc.5(crossws@0.4.1(srvx@0.9.8(patch_hash=f0151386fdcbcb6f53833cf8f66926ef2eb31b71a2782d6e0960dec832ef108d))))
|
||||
'@pixi/app':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/constants':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/core':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/display':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/extensions':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/interaction':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/loaders':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/math':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/runner':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/settings':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)
|
||||
'@pixi/sprite':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/ticker':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))
|
||||
'@pixi/utils':
|
||||
specifier: '6'
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))
|
||||
'@pixiv/three-vrm':
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.4(three@0.182.0)
|
||||
'@pixiv/three-vrm-animation':
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.4(three@0.182.0)
|
||||
'@pixiv/three-vrm-core':
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.4(three@0.182.0)
|
||||
'@proj-airi/audio':
|
||||
specifier: workspace:^
|
||||
version: link:../audio
|
||||
@@ -2217,6 +2166,9 @@ importers:
|
||||
'@proj-airi/stage-shared':
|
||||
specifier: workspace:^
|
||||
version: link:../stage-shared
|
||||
'@proj-airi/stage-ui-live2d':
|
||||
specifier: workspace:^
|
||||
version: link:../stage-ui-live2d
|
||||
'@proj-airi/stage-ui-three':
|
||||
specifier: workspace:^
|
||||
version: link:../stage-ui-three
|
||||
@@ -2310,9 +2262,6 @@ importers:
|
||||
html2canvas:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
localforage:
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
@@ -2331,18 +2280,9 @@ importers:
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
|
||||
pixi-filters:
|
||||
specifier: '4'
|
||||
version: 4.2.0(5af797a0b5bd919a06578c24a7479046)
|
||||
pixi-live2d-display:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0(0736f5bd67d2e4de7e9fff36ad63fa2e)
|
||||
posthog-js:
|
||||
specifier: 'catalog:'
|
||||
version: 1.306.1
|
||||
postprocessing:
|
||||
specifier: ^6.38.2
|
||||
version: 6.38.2(three@0.182.0)
|
||||
rehype-katex:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
@@ -2370,9 +2310,6 @@ importers:
|
||||
splitpanes:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.4(vue@3.5.25(typescript@5.9.3))
|
||||
three:
|
||||
specifier: ^0.182.0
|
||||
version: 0.182.0
|
||||
unified:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5
|
||||
@@ -2497,9 +2434,6 @@ importers:
|
||||
'@types/splitpanes':
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.6
|
||||
'@types/three':
|
||||
specifier: ^0.182.0
|
||||
version: 0.182.0
|
||||
'@types/unist':
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.3
|
||||
@@ -2540,6 +2474,94 @@ importers:
|
||||
specifier: ^3.1.8
|
||||
version: 3.2.1(typescript@5.9.3)
|
||||
|
||||
packages/stage-ui-live2d:
|
||||
dependencies:
|
||||
'@lemonneko/crop-empty-pixels':
|
||||
specifier: 0.1.1
|
||||
version: 0.1.1
|
||||
'@pixi/app':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/constants':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/core':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/display':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/extensions':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/interaction':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/loaders':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/math':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/runner':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10
|
||||
'@pixi/settings':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)
|
||||
'@pixi/sprite':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/ticker':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))
|
||||
'@pixi/utils':
|
||||
specifier: ^6.5.10
|
||||
version: 6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))
|
||||
'@proj-airi/stage-shared':
|
||||
specifier: workspace:^
|
||||
version: link:../stage-shared
|
||||
'@proj-airi/ui':
|
||||
specifier: workspace:^
|
||||
version: link:../ui
|
||||
'@vueuse/core':
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0(vue@3.5.25(typescript@5.9.3))
|
||||
culori:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
es-toolkit:
|
||||
specifier: 'catalog:'
|
||||
version: 1.43.0
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
|
||||
pixi-filters:
|
||||
specifier: '4'
|
||||
version: 4.2.0(5af797a0b5bd919a06578c24a7479046)
|
||||
pixi-live2d-display:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0(0736f5bd67d2e4de7e9fff36ad63fa2e)
|
||||
three:
|
||||
specifier: ^0.182.0
|
||||
version: 0.182.0
|
||||
vue:
|
||||
specifier: ^3.5.25
|
||||
version: 3.5.25(typescript@5.9.3)
|
||||
devDependencies:
|
||||
'@types/culori':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
'@types/three':
|
||||
specifier: ^0.182.0
|
||||
version: 0.182.0
|
||||
vue-tsc:
|
||||
specifier: ^3.1.8
|
||||
version: 3.2.1(typescript@5.9.3)
|
||||
|
||||
packages/stage-ui-three:
|
||||
dependencies:
|
||||
'@pixiv/three-vrm':
|
||||
@@ -2847,7 +2869,7 @@ importers:
|
||||
version: 66.5.11
|
||||
'@wxt-dev/module-vue':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
version: 1.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
vue-tsc:
|
||||
specifier: ^3.1.8
|
||||
version: 3.2.1(typescript@5.9.3)
|
||||
@@ -22068,6 +22090,12 @@ snapshots:
|
||||
vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vitejs/plugin-vue@6.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.53
|
||||
vite: 8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vitest/browser-playwright@4.0.16(bufferutil@4.1.0)(playwright@1.57.0)(utf-8-validate@5.0.10)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.16)':
|
||||
dependencies:
|
||||
'@vitest/browser': 4.0.16(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.16)
|
||||
@@ -22813,9 +22841,9 @@ snapshots:
|
||||
'@types/filesystem': 0.0.36
|
||||
'@types/har-format': 1.2.16
|
||||
|
||||
'@wxt-dev/module-vue@1.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
'@wxt-dev/module-vue@1.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
|
||||
'@vitejs/plugin-vue': 6.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
|
||||
wxt: 0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- vite
|
||||
|
||||
+20
-16
@@ -1,6 +1,3 @@
|
||||
catalogMode: prefer
|
||||
|
||||
shellEmulator: true
|
||||
packages:
|
||||
- packages/**
|
||||
- plugins/**
|
||||
@@ -10,18 +7,6 @@ packages:
|
||||
- apps/**
|
||||
- '!**/dist/**'
|
||||
|
||||
overrides:
|
||||
array-flatten: npm:@nolyfill/array-flatten@^1.0.44
|
||||
axios: npm:feaxios@^0.0.23
|
||||
is-core-module: npm:@nolyfill/is-core-module@^1.0.39
|
||||
isarray: npm:@nolyfill/isarray@^1.0.44
|
||||
safe-buffer: npm:@nolyfill/safe-buffer@^1.0.44
|
||||
safer-buffer: npm:@nolyfill/safer-buffer@^1.0.44
|
||||
side-channel: npm:@nolyfill/side-channel@^1.0.44
|
||||
string.prototype.matchall: npm:@nolyfill/string.prototype.matchall@^1.0.44
|
||||
|
||||
patchedDependencies:
|
||||
srvx@0.9.8: patches/srvx@0.9.8.patch
|
||||
catalog:
|
||||
'@capacitor/cli': ^8.0.0
|
||||
'@capacitor/core': ^8.0.0
|
||||
@@ -74,6 +59,8 @@ catalog:
|
||||
xsschema: 0.4.0-beta.13
|
||||
zod: ^4.2.1
|
||||
|
||||
catalogMode: prefer
|
||||
|
||||
catalogs:
|
||||
rolldown-vite:
|
||||
vite: npm:rolldown-vite@^7.2.11
|
||||
@@ -90,8 +77,9 @@ ignoredBuiltDependencies:
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- '@discordjs/opus'
|
||||
- '@napi-rs/image'
|
||||
- '@ffmpeg-installer/darwin-arm64'
|
||||
- '@ffmpeg-installer/linux-x64'
|
||||
- '@napi-rs/image'
|
||||
- '@parcel/watcher'
|
||||
- bufferutil
|
||||
- core-js
|
||||
@@ -101,6 +89,7 @@ onlyBuiltDependencies:
|
||||
- es5-ext
|
||||
- esbuild
|
||||
- ffmpeg-static
|
||||
- less
|
||||
- msw
|
||||
- onnxruntime-node
|
||||
- protobufjs
|
||||
@@ -109,3 +98,18 @@ onlyBuiltDependencies:
|
||||
- spawn-sync
|
||||
- utf-8-validate
|
||||
- vue-demi
|
||||
|
||||
overrides:
|
||||
array-flatten: npm:@nolyfill/array-flatten@^1.0.44
|
||||
axios: npm:feaxios@^0.0.23
|
||||
is-core-module: npm:@nolyfill/is-core-module@^1.0.39
|
||||
isarray: npm:@nolyfill/isarray@^1.0.44
|
||||
safe-buffer: npm:@nolyfill/safe-buffer@^1.0.44
|
||||
safer-buffer: npm:@nolyfill/safer-buffer@^1.0.44
|
||||
side-channel: npm:@nolyfill/side-channel@^1.0.44
|
||||
string.prototype.matchall: npm:@nolyfill/string.prototype.matchall@^1.0.44
|
||||
|
||||
patchedDependencies:
|
||||
srvx@0.9.8: patches/srvx@0.9.8.patch
|
||||
|
||||
shellEmulator: true
|
||||
|
||||
Reference in New Issue
Block a user