Skip to content

Commit 94f4959

Browse files
fix(stage-ui-three): fix the issue VRM model goes too far #642 (#662)
* bug-fix model goes too far * bug-fix model goes too far gemini-review * Update packages/stage-ui-three/src/composables/vrm/core.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * bug-fix model goes too far gemini-review --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 9c2b9d4 commit 94f4959

File tree

5 files changed

+79
-24
lines changed

5 files changed

+79
-24
lines changed

packages/stage-ui-three/src/components/Controls/OrbitControls.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { onMounted, onUnmounted, shallowRef, toRefs, watch } from 'vue'
2727
* - camera distance: camera position - camera target
2828
*/
2929
const props = defineProps<{
30+
controlEnable: boolean
3031
modelLoaded: boolean
3132
modelSize: Vec3
3233
cameraPosition: Vec3
@@ -48,6 +49,7 @@ const emit = defineEmits<{
4849
}>()
4950
5051
const {
52+
controlEnable,
5153
modelLoaded,
5254
modelSize,
5355
cameraPosition,
@@ -119,6 +121,12 @@ function registerInfoFlow() {
119121
camera.value.updateProjectionMatrix()
120122
controls.value.update()
121123
})
124+
watch(controlEnable, (newEnable) => {
125+
if (!camera.value || !controls.value)
126+
return
127+
controls.value.enableRotate = newEnable
128+
controls.value.enableZoom = newEnable
129+
}, { immediate: true })
122130
123131
/*
124132
* Upward info flow
@@ -160,6 +168,9 @@ onMounted(async () => {
160168
camera.value = cameraTres.value as PerspectiveCamera
161169
// Obtain orbitControl instance
162170
controls.value = new OrbitControls(camera.value, renderer.domElement)
171+
controls.value.enablePan = false
172+
controls.value.enableZoom = false
173+
controls.value.enableRotate = false
163174
// Align to tresjs conventions
164175
controls.value.mouseButtons = {
165176
LEFT: MOUSE.ROTATE,
@@ -170,7 +181,7 @@ onMounted(async () => {
170181
ONE: TOUCH.ROTATE,
171182
TWO: TOUCH.DOLLY_PAN,
172183
}
173-
controls.value.enablePan = false
184+
174185
// define watch props and emit
175186
registerInfoFlow()
176187
controls.value.update()

packages/stage-ui-three/src/components/Model/VRMModel.vue

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* - Load & initialise animation
77
*/
88
9-
import type { VRMCore } from '@pixiv/three-vrm-core'
9+
import type { VRM } from '@pixiv/three-vrm'
1010
import type {
1111
Group,
1212
Object3D,
@@ -29,7 +29,6 @@ import {
2929
MeshPhysicalMaterial,
3030
MeshStandardMaterial,
3131
Plane,
32-
Quaternion,
3332
Raycaster,
3433
3534
SRGBColorSpace,
@@ -146,7 +145,7 @@ const {
146145
147146
// Model and scene ref
148147
const { scene } = useTresContext()
149-
const vrm = shallowRef<VRMCore>()
148+
const vrm = shallowRef<VRM>()
150149
const vrmGroup = shallowRef<Group>()
151150
const modelLoaded = ref<boolean>(false)
152151
// for eye tracking modes
@@ -315,19 +314,7 @@ async function loadModel() {
315314
}
316315
317316
// Set model facing direction
318-
const targetDirection = new Vector3(0, 0, -1) // Default facing direction
319-
const lookAt = _vrm.lookAt
320-
const quaternion = new Quaternion()
321-
if (lookAt) {
322-
const facingDirection = lookAt.faceFront
323-
quaternion.setFromUnitVectors(facingDirection.normalize(), targetDirection.normalize())
324-
_vrmGroup.quaternion.premultiply(quaternion)
325-
_vrmGroup.updateMatrixWorld(true)
326-
}
327-
else {
328-
console.warn('No look-at target found in VRM model')
329-
}
330-
317+
// Lilia: I brought forward the rotation to the core.ts, so that any ad-hoc rotation will not impact the model centre position.
331318
if (isFirstLoad) {
332319
// Reset model rotation Y
333320
emit('modelRotationY', 0)
@@ -436,6 +423,7 @@ async function loadModel() {
436423
idleEyeSaccades.update(vrm.value, lookAtTarget, delta)
437424
vrmEmote.value?.update(delta)
438425
vrmLipSync.update(vrm.value)
426+
vrm.value?.springBoneManager?.update(delta)
439427
}).off
440428
441429
// update the 'last model src'

packages/stage-ui-three/src/components/ThreeScene.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,10 @@ function onOrbitControlsReady() {
132132
133133
// === VRMModel ===
134134
const modelLoaded = ref<boolean>(false)
135+
const controlEnable = ref<boolean>(false)
135136
function onVRMModelLoadStart() {
136137
modelLoaded.value = false
138+
controlEnable.value = false
137139
}
138140
function onVRMModelCameraPosition(value: Vec3) {
139141
cameraPosition.value.x = value.x
@@ -164,6 +166,7 @@ function onVRMModelLookAtTarget(value: Vec3) {
164166
function onVRMModelLoaded(value: string) {
165167
lastModelSrc.value = value
166168
modelLoaded.value = true
169+
controlEnable.value = true
167170
}
168171
169172
// === sky box ===
@@ -289,6 +292,7 @@ defineExpose({
289292
>
290293
<OrbitControls
291294
ref="controlsRef"
295+
:control-enable="controlEnable"
292296
:model-loaded="modelLoaded"
293297
:model-size="modelSize"
294298
:camera-position="cameraPosition"

packages/stage-ui-three/src/composables/vrm/animation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export function reAnchorRootPositionTrack(clip: AnimationClip, _vrm: VRMCore) {
6161

6262
// Calculate the offset from the hips node to the hips's first frame position
6363
const hipsTrack = clip.tracks.find(track =>
64-
track.name.endsWith('Hips.position'),
64+
track instanceof VectorKeyframeTrack
65+
&& track.name === `${hipNode.name}.position`,
6566
)
6667
if (!(hipsTrack instanceof VectorKeyframeTrack)) {
6768
console.warn('No Hips.position track of type VectorKeyframeTrack found in animation.')

packages/stage-ui-three/src/composables/vrm/core.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import type { VRMCore } from '@pixiv/three-vrm'
2-
import type { Object3D, Scene } from 'three'
1+
import type { VRM, VRMCore } from '@pixiv/three-vrm'
2+
import type { Mesh, Object3D, Scene } from 'three'
33

44
import { VRMUtils } from '@pixiv/three-vrm'
55
import { VRMLookAtQuaternionProxy } from '@pixiv/three-vrm-animation'
6-
import { Box3, Group, Vector3 } from 'three'
6+
import { Box3, Group, Quaternion, Vector3 } from 'three'
77

88
import { useVRMLoader } from './loader'
99

@@ -16,7 +16,7 @@ export async function loadVrm(model: string, options?: {
1616
lookAt?: boolean
1717
onProgress?: (progress: ProgressEvent<EventTarget>) => void | Promise<void>
1818
}): Promise<{
19-
_vrm: VRMCore
19+
_vrm: VRM
2020
_vrmGroup: Group
2121
modelCenter: Vector3
2222
modelSize: Vector3
@@ -55,7 +55,58 @@ export async function loadVrm(model: string, options?: {
5555
options.scene.add(_vrmGroup)
5656
}
5757

58-
const box = new Box3().setFromObject(_vrm.scene)
58+
// Preset the facing direction
59+
const targetDirection = new Vector3(0, 0, -1) // Default facing direction
60+
const lookAt = _vrm.lookAt
61+
const quaternion = new Quaternion()
62+
if (lookAt) {
63+
const facingDirection = lookAt.faceFront
64+
quaternion.setFromUnitVectors(facingDirection.normalize(), targetDirection.normalize())
65+
_vrmGroup.quaternion.premultiply(quaternion)
66+
_vrmGroup.updateMatrixWorld(true)
67+
}
68+
else {
69+
console.warn('No look-at target found in VRM model')
70+
}
71+
(_vrm as VRM).springBoneManager?.reset()
72+
_vrmGroup.updateMatrixWorld(true)
73+
74+
function computeBoundingBox(vrm: Object3D) {
75+
const box = new Box3()
76+
const childBox = new Box3()
77+
78+
vrm.updateMatrixWorld(true)
79+
80+
vrm.traverse((obj) => {
81+
if (!obj.visible)
82+
return
83+
const mesh = obj as Mesh
84+
if (!mesh.isMesh)
85+
return
86+
if (!mesh.geometry)
87+
return
88+
// This traverse mesh console print will be important for future debugging
89+
// console.debug("mesh node: ", mesh)
90+
91+
// Selectively filter out VRM spring bone colliders
92+
if (mesh.name.startsWith('VRMC_springBone_collider'))
93+
return
94+
95+
const geometry = mesh.geometry
96+
if (!geometry.boundingBox) {
97+
geometry.computeBoundingBox()
98+
}
99+
100+
childBox.copy(geometry.boundingBox!)
101+
childBox.applyMatrix4(mesh.matrixWorld)
102+
103+
box.union(childBox)
104+
})
105+
106+
return box
107+
}
108+
109+
const box = computeBoundingBox(_vrm.scene)
59110
const modelSize = new Vector3()
60111
const modelCenter = new Vector3()
61112
box.getSize(modelSize)
@@ -68,7 +119,7 @@ export async function loadVrm(model: string, options?: {
68119
const radians = (fov / 2 * Math.PI) / 180
69120
const initialCameraOffset = new Vector3(
70121
modelSize.x / 16,
71-
modelSize.y / 6, // default y value
122+
modelSize.y / 8, // default y value
72123
-(modelSize.y / 3) / Math.tan(radians), // default z value
73124
)
74125

0 commit comments

Comments
 (0)