refactor(stage-ui): VRM & Live2D view load
This commit is contained in:
@@ -50,6 +50,7 @@ modelFileDialog.onChange((files) => {
|
||||
motionMap.value = {}
|
||||
modelFile.value = files[0]
|
||||
loadingModel.value = true
|
||||
live2d.shouldUpdateView()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ const trackingOptions = computed(() => [
|
||||
modelFileDialog.onChange((files) => {
|
||||
if (files && files.length > 0) {
|
||||
modelFile.value = files[0]
|
||||
vrm.shouldUpdateView()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -151,7 +151,12 @@ async function loadModel() {
|
||||
}
|
||||
|
||||
const modelInstance = new Live2DModel<PixiLive2DInternalModel>()
|
||||
await Live2DFactory.setupLive2DModel(modelInstance, modelSrcNormalized.value, { autoInteract: false })
|
||||
if (modelFile.value) {
|
||||
await Live2DFactory.setupLive2DModel(modelInstance, [modelFile.value], { autoInteract: false })
|
||||
}
|
||||
else {
|
||||
await Live2DFactory.setupLive2DModel(modelInstance, modelSrcNormalized.value, { autoInteract: false })
|
||||
}
|
||||
|
||||
model.value = modelInstance
|
||||
pixiApp.value.stage.addChild(model.value)
|
||||
@@ -270,7 +275,7 @@ function updateDropShadowFilter() {
|
||||
}
|
||||
|
||||
watch([() => props.width, () => props.height], () => handleResize())
|
||||
watch(modelSrcNormalized, () => loadModel(), { immediate: true })
|
||||
watch(modelSrcNormalized, () => loadModel())
|
||||
watch(dark, updateDropShadowFilter, { immediate: true })
|
||||
watch([model, themeColorsHue], updateDropShadowFilter)
|
||||
watch(offset, setScaleAndPosition)
|
||||
|
||||
@@ -15,7 +15,13 @@ export async function loadVrm(model: string, options?: {
|
||||
scene?: Scene
|
||||
lookAt?: boolean
|
||||
onProgress?: (progress: ProgressEvent<EventTarget>) => void | Promise<void>
|
||||
}): Promise<{ _vrm: VRMCore, _vrmGroup: Group, modelCenter: Vector3, modelSize: Vector3, initialCameraOffset: Vector3 } | undefined> {
|
||||
}): Promise<{
|
||||
_vrm: VRMCore
|
||||
_vrmGroup: Group
|
||||
modelCenter: Vector3
|
||||
modelSize: Vector3
|
||||
initialCameraOffset: Vector3
|
||||
} | undefined> {
|
||||
const loader = useVRMLoader()
|
||||
const gltf = await loader.loadAsync(model, progress => options?.onProgress?.(progress))
|
||||
|
||||
|
||||
@@ -1,13 +1,65 @@
|
||||
import localforage from 'localforage'
|
||||
|
||||
import { computedAsync, useLocalStorage } from '@vueuse/core'
|
||||
import { useBroadcastChannel, useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
type BroadcastChannelEvents
|
||||
= | BroadcastChannelEventShouldUpdateView
|
||||
|
||||
interface BroadcastChannelEventShouldUpdateView {
|
||||
type: 'should-update-view'
|
||||
}
|
||||
|
||||
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 indexedDbModelFile = ref<File | null>(null)
|
||||
|
||||
onMounted(async () => await loadModelFileFromIndexedDb())
|
||||
onShouldUpdateView(async () => await loadModelFileFromIndexedDb())
|
||||
|
||||
async function loadModelFileFromIndexedDb() {
|
||||
const file = await localforage.getItem<File>('assets-models-live2d')
|
||||
if (file) {
|
||||
indexedDbModelFile.value = file
|
||||
}
|
||||
}
|
||||
|
||||
const modelFile = computed({
|
||||
get: () => {
|
||||
return indexedDbModelFile.value
|
||||
},
|
||||
set: (file: File | null) => {
|
||||
if (file) {
|
||||
localforage.setItem('assets-models-live2d', file)
|
||||
}
|
||||
else {
|
||||
localforage.removeItem('assets-models-live2d')
|
||||
}
|
||||
|
||||
indexedDbModelFile.value = file
|
||||
},
|
||||
})
|
||||
|
||||
const defaultModelUrl = '/assets/live2d/models/hiyori_pro_zh.zip'
|
||||
const modelUrl = useLocalStorage<string>('settings/live2d/model-src', defaultModelUrl)
|
||||
const modelFile = computedAsync(async () => localforage.getItem<File>('assets-models-live2d'))
|
||||
|
||||
const loadingModel = ref(false) // if set to true, the model will be loaded
|
||||
const position = useLocalStorage('settings/live2d/position', { x: 0, y: 0 }) // position is relative to the center of the screen, units are %
|
||||
@@ -30,5 +82,8 @@ export const useLive2d = defineStore('live2d', () => {
|
||||
availableMotions,
|
||||
motionMap,
|
||||
scale,
|
||||
|
||||
onShouldUpdateView,
|
||||
shouldUpdateView,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
import localforage from 'localforage'
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { useBroadcastChannel, useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import defaultSkyBoxSrc from '../components/Scenes/Tres/assets/sky_linekotsi_23_HDRI.hdr?url'
|
||||
|
||||
type BroadcastChannelEvents
|
||||
= | BroadcastChannelEventShouldUpdateView
|
||||
|
||||
interface BroadcastChannelEventShouldUpdateView {
|
||||
type: 'should-update-view'
|
||||
}
|
||||
|
||||
export const useVRM = defineStore('vrm', () => {
|
||||
const { post, data } = useBroadcastChannel<BroadcastChannelEvents, BroadcastChannelEvents>({ name: 'airi-stores-vrm' })
|
||||
const shouldUpdateViewHooks = ref<Array<() => void | Promise<void>>>([])
|
||||
|
||||
const onShouldUpdateView = (hook: () => void | Promise<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 indexedDbModelFile = ref<File | null>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
async function loadModelFileFromIndexedDb() {
|
||||
const file = await localforage.getItem<File>('assets-models-vrm')
|
||||
if (file) {
|
||||
indexedDbModelFile.value = file
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => loadModelFileFromIndexedDb())
|
||||
// onShouldUpdateView(() => loadModelFileFromIndexedDb())
|
||||
|
||||
const modelFile = computed({
|
||||
get: () => {
|
||||
@@ -121,9 +149,10 @@ export const useVRM = defineStore('vrm', () => {
|
||||
isTracking,
|
||||
trackingMode,
|
||||
eyeHeight,
|
||||
|
||||
envSelect,
|
||||
skyBoxSrc,
|
||||
|
||||
shouldUpdateView,
|
||||
onShouldUpdateView,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user