1
0

VRMAnimationLoaderPlugin.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import * as THREE from "three";
  2. import {
  3. GLTF,
  4. GLTFLoaderPlugin,
  5. GLTFParser,
  6. } from "three/examples/jsm/loaders/GLTFLoader.js";
  7. import { VRMAnimationLoaderPluginOptions } from "./VRMAnimationLoaderPluginOptions";
  8. import { GLTF as GLTFSchema } from "@gltf-transform/core";
  9. import { VRMCVRMAnimation } from "./VRMCVRMAnimation";
  10. import { VRMHumanBoneName, VRMHumanBoneParentMap } from "@pixiv/three-vrm";
  11. import { VRMAnimation } from "./VRMAnimation";
  12. import { arrayChunk } from "./utils/arrayChunk";
  13. const MAT4_IDENTITY = new THREE.Matrix4();
  14. const _v3A = new THREE.Vector3();
  15. const _quatA = new THREE.Quaternion();
  16. const _quatB = new THREE.Quaternion();
  17. const _quatC = new THREE.Quaternion();
  18. interface VRMAnimationLoaderPluginNodeMap {
  19. humanoidIndexToName: Map<number, VRMHumanBoneName>;
  20. expressionsIndexToName: Map<number, string>;
  21. lookAtIndex: number | null;
  22. }
  23. type VRMAnimationLoaderPluginWorldMatrixMap = Map<
  24. VRMHumanBoneName | "hipsParent",
  25. THREE.Matrix4
  26. >;
  27. export class VRMAnimationLoaderPlugin implements GLTFLoaderPlugin {
  28. public readonly parser: GLTFParser;
  29. public constructor(
  30. parser: GLTFParser,
  31. options?: VRMAnimationLoaderPluginOptions
  32. ) {
  33. this.parser = parser;
  34. }
  35. public get name(): string {
  36. return "VRMC_vrm_animation";
  37. }
  38. public async afterRoot(gltf: GLTF): Promise<void> {
  39. const defGltf = gltf.parser.json as GLTFSchema.IGLTF;
  40. const defExtensionsUsed = defGltf.extensionsUsed;
  41. if (
  42. defExtensionsUsed == null ||
  43. defExtensionsUsed.indexOf(this.name) == -1
  44. ) {
  45. return;
  46. }
  47. const defExtension = defGltf.extensions?.[this.name] as
  48. | VRMCVRMAnimation
  49. | undefined;
  50. if (defExtension == null) {
  51. return;
  52. }
  53. const nodeMap = this._createNodeMap(defExtension);
  54. const worldMatrixMap = await this._createBoneWorldMatrixMap(
  55. gltf,
  56. defExtension
  57. );
  58. const hipsNode = defExtension.humanoid.humanBones["hips"]!.node;
  59. const hips = (await gltf.parser.getDependency(
  60. "node",
  61. hipsNode
  62. )) as THREE.Object3D;
  63. const restHipsPosition = hips.getWorldPosition(new THREE.Vector3());
  64. const clips = gltf.animations;
  65. const animations: VRMAnimation[] = clips.map((clip, iAnimation) => {
  66. const defAnimation = defGltf.animations![iAnimation];
  67. const animation = this._parseAnimation(
  68. clip,
  69. defAnimation,
  70. nodeMap,
  71. worldMatrixMap
  72. );
  73. animation.restHipsPosition = restHipsPosition;
  74. return animation;
  75. });
  76. gltf.userData.vrmAnimations = animations;
  77. }
  78. private _createNodeMap(
  79. defExtension: VRMCVRMAnimation
  80. ): VRMAnimationLoaderPluginNodeMap {
  81. const humanoidIndexToName: Map<number, VRMHumanBoneName> = new Map();
  82. const expressionsIndexToName: Map<number, string> = new Map();
  83. let lookAtIndex: number | null;
  84. // humanoid
  85. const humanBones = defExtension.humanoid?.humanBones;
  86. if (humanBones) {
  87. Object.entries(humanBones).forEach(([name, bone]) => {
  88. const { node } = bone;
  89. humanoidIndexToName.set(node, name as VRMHumanBoneName);
  90. });
  91. }
  92. // expressions
  93. const preset = defExtension.expressions?.preset;
  94. if (preset) {
  95. Object.entries(preset).forEach(([name, expression]) => {
  96. const { node } = expression;
  97. expressionsIndexToName.set(node, name as string);
  98. });
  99. }
  100. const custom = defExtension.expressions?.custom;
  101. if (custom) {
  102. Object.entries(custom).forEach(([name, expression]) => {
  103. const { node } = expression;
  104. expressionsIndexToName.set(node, name as string);
  105. });
  106. }
  107. // lookAt
  108. lookAtIndex = defExtension.lookAt?.node ?? null;
  109. return { humanoidIndexToName, expressionsIndexToName, lookAtIndex };
  110. }
  111. private async _createBoneWorldMatrixMap(
  112. gltf: GLTF,
  113. defExtension: VRMCVRMAnimation
  114. ): Promise<VRMAnimationLoaderPluginWorldMatrixMap> {
  115. // update the entire hierarchy first
  116. gltf.scene.updateWorldMatrix(false, true);
  117. const threeNodes = (await gltf.parser.getDependencies(
  118. "node"
  119. )) as THREE.Object3D[];
  120. const worldMatrixMap: VRMAnimationLoaderPluginWorldMatrixMap = new Map();
  121. for (const [boneName, { node }] of Object.entries(
  122. defExtension.humanoid.humanBones
  123. )) {
  124. const threeNode = threeNodes[node];
  125. worldMatrixMap.set(boneName as VRMHumanBoneName, threeNode.matrixWorld);
  126. if (boneName === "hips") {
  127. worldMatrixMap.set(
  128. "hipsParent",
  129. threeNode.parent?.matrixWorld ?? MAT4_IDENTITY
  130. );
  131. }
  132. }
  133. return worldMatrixMap;
  134. }
  135. private _parseAnimation(
  136. animationClip: THREE.AnimationClip,
  137. defAnimation: GLTFSchema.IAnimation,
  138. nodeMap: VRMAnimationLoaderPluginNodeMap,
  139. worldMatrixMap: VRMAnimationLoaderPluginWorldMatrixMap
  140. ): VRMAnimation {
  141. const tracks = animationClip.tracks;
  142. const defChannels = defAnimation.channels;
  143. const result = new VRMAnimation();
  144. result.duration = animationClip.duration;
  145. defChannels.forEach((channel, iChannel) => {
  146. const { node, path } = channel.target;
  147. const origTrack = tracks[iChannel];
  148. if (node == null) {
  149. return;
  150. }
  151. // humanoid
  152. const boneName = nodeMap.humanoidIndexToName.get(node);
  153. if (boneName != null) {
  154. let parentBoneName: VRMHumanBoneName | "hipsParent" | null =
  155. VRMHumanBoneParentMap[boneName];
  156. while (
  157. parentBoneName != null &&
  158. worldMatrixMap.get(parentBoneName) == null
  159. ) {
  160. parentBoneName = VRMHumanBoneParentMap[parentBoneName];
  161. }
  162. parentBoneName ??= "hipsParent";
  163. if (path === "translation") {
  164. const hipsParentWorldMatrix = worldMatrixMap.get("hipsParent")!;
  165. const trackValues = arrayChunk(origTrack.values, 3).flatMap((v) =>
  166. _v3A.fromArray(v).applyMatrix4(hipsParentWorldMatrix).toArray()
  167. );
  168. const track = origTrack.clone();
  169. track.values = new Float32Array(trackValues);
  170. result.humanoidTracks.translation.set(boneName, track);
  171. } else if (path === "rotation") {
  172. // a = p^-1 * a' * p * c
  173. // a' = p * p^-1 * a' * p * c * c^-1 * p^-1
  174. // = p * a * c^-1 * p^-1
  175. const worldMatrix = worldMatrixMap.get(boneName)!;
  176. const parentWorldMatrix = worldMatrixMap.get(parentBoneName)!;
  177. _quatA.setFromRotationMatrix(worldMatrix).normalize().invert();
  178. _quatB.setFromRotationMatrix(parentWorldMatrix).normalize();
  179. const trackValues = arrayChunk(origTrack.values, 4).flatMap((q) =>
  180. _quatC.fromArray(q).premultiply(_quatB).multiply(_quatA).toArray()
  181. );
  182. const track = origTrack.clone();
  183. track.values = new Float32Array(trackValues);
  184. result.humanoidTracks.rotation.set(boneName, track);
  185. } else {
  186. throw new Error(`Invalid path "${path}"`);
  187. }
  188. return;
  189. }
  190. // expressions
  191. const expressionName = nodeMap.expressionsIndexToName.get(node);
  192. if (expressionName != null) {
  193. if (path === "translation") {
  194. const times = origTrack.times;
  195. const values = new Float32Array(origTrack.values.length / 3);
  196. for (let i = 0; i < values.length; i++) {
  197. values[i] = origTrack.values[3 * i];
  198. }
  199. const newTrack = new THREE.NumberKeyframeTrack(
  200. `${expressionName}.weight`,
  201. times as any,
  202. values as any
  203. );
  204. result.expressionTracks.set(expressionName, newTrack);
  205. } else {
  206. throw new Error(`Invalid path "${path}"`);
  207. }
  208. return;
  209. }
  210. // lookAt
  211. if (node === nodeMap.lookAtIndex) {
  212. if (path === "rotation") {
  213. result.lookAtTrack = origTrack;
  214. } else {
  215. throw new Error(`Invalid path "${path}"`);
  216. }
  217. }
  218. });
  219. return result;
  220. }
  221. }