-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcomponent.html
More file actions
186 lines (156 loc) · 7.2 KB
/
component.html
File metadata and controls
186 lines (156 loc) · 7.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<!DOCTYPE html>
<html>
<head>
<title>A-Frame VRMA Component</title>
<meta name="description" content="A single component to play a VRM model with a VRMA animation and leg correction">
<script src="js/aframe-v1.7.1.js"></script>
<script src="js/aframe-environment-component.js"></script>
<script src="js/three-vrm.js"></script>
<script src="js/three-vrm-animation.js"></script>
<script src="js/aframe-vrm-bundle.js"></script>
</head>
<body>
<script>
AFRAME.registerComponent('a-frame-vrma', {
schema: {
vrm: { type: 'string', default: '' },
anim: { type: 'string', default: '' },
legSpread: { type: 'number', default: -0.5 }
},
init: function () {
this.vrm = null;
this.mixer = null;
this.currentAction = null;
// For leg fixing
this.leftUpperLeg = null;
this.rightUpperLeg = null;
this.zAxis = new THREE.Vector3(0, 0, 1);
this.leftOffset = new THREE.Quaternion();
this.rightOffset = new THREE.Quaternion();
// Bind methods
this.onModelLoaded = this.onModelLoaded.bind(this);
// Listen for the VRM model loading completion
this.el.addEventListener('model-loaded', this.onModelLoaded);
},
update: function (oldData) {
// If the VRM source changes, update the vrm component
if (this.data.vrm && this.data.vrm !== oldData.vrm) {
// We delegate the actual model loading to the existing 'vrm' component
this.el.setAttribute('vrm', 'src', this.data.vrm);
}
// If the animation source changes and we already have a model, load the new animation
if (this.data.anim && this.data.anim !== oldData.anim && this.vrm) {
this.loadAnimation(this.data.anim);
}
},
onModelLoaded: function (evt) {
// Ensure this is the event for our VRM
const vrm = evt.detail.vrm;
if (!vrm) return;
console.log('VRM Model loaded:', this.data.vrm);
this.vrm = vrm;
// Get the mixer from the vrm component or create/find one
// The aframe-vrm-bundle usually attaches the mixer to el.components.vrm.mixer
if (this.el.components.vrm && this.el.components.vrm.mixer) {
this.mixer = this.el.components.vrm.mixer;
} else {
console.warn('Mixer not found in vrm component, creating new one.');
this.mixer = new THREE.AnimationMixer(this.vrm.scene);
}
// Setup leg fixing
if (this.vrm.humanoid) {
this.leftUpperLeg = this.vrm.humanoid.getBoneNode('leftUpperLeg');
this.rightUpperLeg = this.vrm.humanoid.getBoneNode('rightUpperLeg');
}
// Now that model is ready, load animation if specified
if (this.data.anim) {
this.loadAnimation(this.data.anim);
}
},
loadAnimation: async function (animUrl) {
if (!this.vrm || !this.mixer) {
console.warn('Cannot load animation: VRM or Mixer not ready.');
return;
}
console.log('Loading VRMA:', animUrl);
const loader = new THREE.GLTFLoader();
loader.register((parser) => {
const plugin = new THREE.VRMAnimationLoaderPlugin(parser);
const originalAfterRoot = plugin.afterRoot.bind(plugin);
plugin.afterRoot = async function (gltf) {
// Shim to ensure spec version if missing, common in some older VRMA files
if (gltf.parser.json.extensions && gltf.parser.json.extensions.VRMC_vrm_animation) {
const vrmcAnim = gltf.parser.json.extensions.VRMC_vrm_animation;
if (!vrmcAnim.specVersion) {
vrmcAnim.specVersion = '1.0';
}
}
return originalAfterRoot(gltf);
};
return plugin;
});
try {
const gltf = await loader.loadAsync(animUrl);
const vrmAnimations = gltf.userData.vrmAnimations;
if (vrmAnimations && vrmAnimations.length > 0) {
const vrmAnimation = vrmAnimations[0];
const clip = THREE.createVRMAnimationClip(vrmAnimation, this.vrm);
if (clip) {
// Stop any existing action
if (this.currentAction) {
this.currentAction.stop();
}
this.currentAction = this.mixer.clipAction(clip);
this.currentAction.loop = THREE.LoopRepeat;
this.currentAction.clampWhenFinished = false;
this.currentAction.play();
console.log('Animation playing.');
}
} else {
console.warn('No VRM animations found in file.');
}
} catch (err) {
console.error('Error loading VRMA:', err);
}
},
tick: function (time, deltaTime) {
// Apply leg fix every frame AFTER animation has updated bones
// Note: The vrm component's tick generally runs before this if components are initialized in order,
// or we rely on A-Frame component execution order.
// If the mixer update happens in 'vrm' component tick, we need to modify bones afterwards.
if (this.leftUpperLeg && this.rightUpperLeg && this.data.legSpread !== 0) {
const spreadAngle = this.data.legSpread * Math.PI / 4;
this.leftOffset.setFromAxisAngle(this.zAxis, spreadAngle);
this.rightOffset.setFromAxisAngle(this.zAxis, -spreadAngle);
this.leftUpperLeg.quaternion.premultiply(this.leftOffset);
this.rightUpperLeg.quaternion.premultiply(this.rightOffset);
}
},
remove: function () {
this.el.removeEventListener('model-loaded', this.onModelLoaded);
if (this.currentAction) {
this.currentAction.stop();
}
}
});
</script>
<a-scene>
<a-sky color="#001a33"></a-sky>
<a-plane position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#003366" shadow="receive: true"></a-plane>
<a-entity light="type: ambient; intensity: 0.5;"></a-entity>
<a-entity light="type: directional; intensity: 0.8; castShadow: true;" position="2 4 2"></a-entity>
<a-entity position="0 1.6 2">
<a-camera></a-camera>
</a-entity>
<!-- Usage Example -->
<!-- The component handles setting the vrm='src: ...' internally based on the attribute -->
<a-entity
id="my-avatar"
a-frame-vrma="vrm: models/Cop.vrm; anim: vrma/01_01.vrma; legSpread: -0.5"
position="0 0 -2"
rotation="0 180 0"
shadow="cast: true">
</a-entity>
</a-scene>
</body>
</html>