diff --git a/packages/stage-ui-three/src/components/Controls/OrbitControls.vue b/packages/stage-ui-three/src/components/Controls/OrbitControls.vue index 46b9e500a..d5d93fe91 100644 --- a/packages/stage-ui-three/src/components/Controls/OrbitControls.vue +++ b/packages/stage-ui-three/src/components/Controls/OrbitControls.vue @@ -27,6 +27,7 @@ import { onMounted, onUnmounted, shallowRef, toRefs, watch } from 'vue' * - camera distance: camera position - camera target */ const props = defineProps<{ + controlEnable: boolean modelLoaded: boolean modelSize: Vec3 cameraPosition: Vec3 @@ -48,6 +49,7 @@ const emit = defineEmits<{ }>() const { + controlEnable, modelLoaded, modelSize, cameraPosition, @@ -119,6 +121,12 @@ function registerInfoFlow() { camera.value.updateProjectionMatrix() controls.value.update() }) + watch(controlEnable, (newEnable) => { + if (!camera.value || !controls.value) + return + controls.value.enableRotate = newEnable + controls.value.enableZoom = newEnable + }, { immediate: true }) /* * Upward info flow @@ -160,6 +168,9 @@ onMounted(async () => { camera.value = cameraTres.value as PerspectiveCamera // Obtain orbitControl instance controls.value = new OrbitControls(camera.value, renderer.domElement) + controls.value.enablePan = false + controls.value.enableZoom = false + controls.value.enableRotate = false // Align to tresjs conventions controls.value.mouseButtons = { LEFT: MOUSE.ROTATE, @@ -170,7 +181,7 @@ onMounted(async () => { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN, } - controls.value.enablePan = false + // define watch props and emit registerInfoFlow() controls.value.update() diff --git a/packages/stage-ui-three/src/components/Model/VRMModel.vue b/packages/stage-ui-three/src/components/Model/VRMModel.vue index 7da52dfc7..b721c43de 100644 --- a/packages/stage-ui-three/src/components/Model/VRMModel.vue +++ b/packages/stage-ui-three/src/components/Model/VRMModel.vue @@ -6,7 +6,7 @@ * - Load & initialise animation */ -import type { VRMCore } from '@pixiv/three-vrm-core' +import type { VRM } from '@pixiv/three-vrm' import type { Group, Object3D, @@ -29,7 +29,6 @@ import { MeshPhysicalMaterial, MeshStandardMaterial, Plane, - Quaternion, Raycaster, SRGBColorSpace, @@ -146,7 +145,7 @@ const { // Model and scene ref const { scene } = useTresContext() -const vrm = shallowRef() +const vrm = shallowRef() const vrmGroup = shallowRef() const modelLoaded = ref(false) // for eye tracking modes @@ -315,19 +314,7 @@ async function loadModel() { } // Set model facing direction - const targetDirection = new Vector3(0, 0, -1) // Default facing direction - const lookAt = _vrm.lookAt - const quaternion = new Quaternion() - if (lookAt) { - const facingDirection = lookAt.faceFront - quaternion.setFromUnitVectors(facingDirection.normalize(), targetDirection.normalize()) - _vrmGroup.quaternion.premultiply(quaternion) - _vrmGroup.updateMatrixWorld(true) - } - else { - console.warn('No look-at target found in VRM model') - } - + // Lilia: I brought forward the rotation to the core.ts, so that any ad-hoc rotation will not impact the model centre position. if (isFirstLoad) { // Reset model rotation Y emit('modelRotationY', 0) @@ -436,6 +423,7 @@ async function loadModel() { idleEyeSaccades.update(vrm.value, lookAtTarget, delta) vrmEmote.value?.update(delta) vrmLipSync.update(vrm.value) + vrm.value?.springBoneManager?.update(delta) }).off // update the 'last model src' diff --git a/packages/stage-ui-three/src/components/ThreeScene.vue b/packages/stage-ui-three/src/components/ThreeScene.vue index 6bcae92a7..f1ef616e8 100644 --- a/packages/stage-ui-three/src/components/ThreeScene.vue +++ b/packages/stage-ui-three/src/components/ThreeScene.vue @@ -132,8 +132,10 @@ function onOrbitControlsReady() { // === VRMModel === const modelLoaded = ref(false) +const controlEnable = ref(false) function onVRMModelLoadStart() { modelLoaded.value = false + controlEnable.value = false } function onVRMModelCameraPosition(value: Vec3) { cameraPosition.value.x = value.x @@ -164,6 +166,7 @@ function onVRMModelLookAtTarget(value: Vec3) { function onVRMModelLoaded(value: string) { lastModelSrc.value = value modelLoaded.value = true + controlEnable.value = true } // === sky box === @@ -289,6 +292,7 @@ defineExpose({ > - track.name.endsWith('Hips.position'), + track instanceof VectorKeyframeTrack + && track.name === `${hipNode.name}.position`, ) if (!(hipsTrack instanceof VectorKeyframeTrack)) { console.warn('No Hips.position track of type VectorKeyframeTrack found in animation.') diff --git a/packages/stage-ui-three/src/composables/vrm/core.ts b/packages/stage-ui-three/src/composables/vrm/core.ts index a7d5d1d0d..9b66396d9 100644 --- a/packages/stage-ui-three/src/composables/vrm/core.ts +++ b/packages/stage-ui-three/src/composables/vrm/core.ts @@ -1,9 +1,9 @@ -import type { VRMCore } from '@pixiv/three-vrm' -import type { Object3D, Scene } from 'three' +import type { VRM, VRMCore } from '@pixiv/three-vrm' +import type { Mesh, Object3D, Scene } from 'three' import { VRMUtils } from '@pixiv/three-vrm' import { VRMLookAtQuaternionProxy } from '@pixiv/three-vrm-animation' -import { Box3, Group, Vector3 } from 'three' +import { Box3, Group, Quaternion, Vector3 } from 'three' import { useVRMLoader } from './loader' @@ -16,7 +16,7 @@ export async function loadVrm(model: string, options?: { lookAt?: boolean onProgress?: (progress: ProgressEvent) => void | Promise }): Promise<{ - _vrm: VRMCore + _vrm: VRM _vrmGroup: Group modelCenter: Vector3 modelSize: Vector3 @@ -55,7 +55,58 @@ export async function loadVrm(model: string, options?: { options.scene.add(_vrmGroup) } - const box = new Box3().setFromObject(_vrm.scene) + // Preset the facing direction + const targetDirection = new Vector3(0, 0, -1) // Default facing direction + const lookAt = _vrm.lookAt + const quaternion = new Quaternion() + if (lookAt) { + const facingDirection = lookAt.faceFront + quaternion.setFromUnitVectors(facingDirection.normalize(), targetDirection.normalize()) + _vrmGroup.quaternion.premultiply(quaternion) + _vrmGroup.updateMatrixWorld(true) + } + else { + console.warn('No look-at target found in VRM model') + } + (_vrm as VRM).springBoneManager?.reset() + _vrmGroup.updateMatrixWorld(true) + + function computeBoundingBox(vrm: Object3D) { + const box = new Box3() + const childBox = new Box3() + + vrm.updateMatrixWorld(true) + + vrm.traverse((obj) => { + if (!obj.visible) + return + const mesh = obj as Mesh + if (!mesh.isMesh) + return + if (!mesh.geometry) + return + // This traverse mesh console print will be important for future debugging + // console.debug("mesh node: ", mesh) + + // Selectively filter out VRM spring bone colliders + if (mesh.name.startsWith('VRMC_springBone_collider')) + return + + const geometry = mesh.geometry + if (!geometry.boundingBox) { + geometry.computeBoundingBox() + } + + childBox.copy(geometry.boundingBox!) + childBox.applyMatrix4(mesh.matrixWorld) + + box.union(childBox) + }) + + return box + } + + const box = computeBoundingBox(_vrm.scene) const modelSize = new Vector3() const modelCenter = new Vector3() box.getSize(modelSize) @@ -68,7 +119,7 @@ export async function loadVrm(model: string, options?: { const radians = (fov / 2 * Math.PI) / 180 const initialCameraOffset = new Vector3( modelSize.x / 16, - modelSize.y / 6, // default y value + modelSize.y / 8, // default y value -(modelSize.y / 3) / Math.tan(radians), // default z value )