(function (global, factory) { if (typeof define === "function" && define.amd) { define(["exports", "three", "../animation/CCDIKSolver.js", "../animation/MMDPhysics.js"], factory); } else if (typeof exports !== "undefined") { factory(exports, require("three"), require("../animation/CCDIKSolver.js"), require("../animation/MMDPhysics.js")); } else { var mod = { exports: {} }; factory(mod.exports, global.three, global.CCDIKSolver, global.MMDPhysics); global.MMDAnimationHelper = mod.exports; } })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _three, _CCDIKSolver, _MMDPhysics) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.MMDAnimationHelper = void 0; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } /** * MMDAnimationHelper handles animation of MMD assets loaded by MMDLoader * with MMD special features as IK, Grant, and Physics. * * Dependencies * - ammo.js https://github.com/kripken/ammo.js * - MMDPhysics * - CCDIKSolver * * TODO * - more precise grant skinning support. */ var MMDAnimationHelper = /*#__PURE__*/function () { /** * @param {Object} params - (optional) * @param {boolean} params.sync - Whether animation durations of added objects are synched. Default is true. * @param {Number} params.afterglow - Default is 0.0. * @param {boolean} params.resetPhysicsOnLoop - Default is true. */ function MMDAnimationHelper() { var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, MMDAnimationHelper); this.meshes = []; this.camera = null; this.cameraTarget = new _three.Object3D(); this.cameraTarget.name = 'target'; this.audio = null; this.audioManager = null; this.objects = new WeakMap(); this.configuration = { sync: params.sync !== undefined ? params.sync : true, afterglow: params.afterglow !== undefined ? params.afterglow : 0.0, resetPhysicsOnLoop: params.resetPhysicsOnLoop !== undefined ? params.resetPhysicsOnLoop : true, pmxAnimation: params.pmxAnimation !== undefined ? params.pmxAnimation : false }; this.enabled = { animation: true, ik: true, grant: true, physics: true, cameraAnimation: true }; this.onBeforePhysics = function /* mesh */ () {}; // experimental this.sharedPhysics = false; this.masterPhysics = null; } /** * Adds an Three.js Object to helper and setups animation. * The anmation durations of added objects are synched * if this.configuration.sync is true. * * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object * @param {Object} params - (optional) * @param {THREE.AnimationClip|Array} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined. * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true. * @param {Integer} params.warmup - Only for THREE.SkinnedMesh and physics is true. Default is 60. * @param {Number} params.unitStep - Only for THREE.SkinnedMesh and physics is true. Default is 1 / 65. * @param {Integer} params.maxStepNum - Only for THREE.SkinnedMesh and physics is true. Default is 3. * @param {Vector3} params.gravity - Only for THREE.SkinnedMesh and physics is true. Default ( 0, - 9.8 * 10, 0 ). * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0. * @return {MMDAnimationHelper} */ _createClass(MMDAnimationHelper, [{ key: "add", value: function add(object) { var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (object.isSkinnedMesh) { this._addMesh(object, params); } else if (object.isCamera) { this._setupCamera(object, params); } else if (object.type === 'Audio') { this._setupAudio(object, params); } else { throw new Error('THREE.MMDAnimationHelper.add: ' + 'accepts only ' + 'THREE.SkinnedMesh or ' + 'THREE.Camera or ' + 'THREE.Audio instance.'); } if (this.configuration.sync) this._syncDuration(); return this; } /** * Removes an Three.js Object from helper. * * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object * @return {MMDAnimationHelper} */ }, { key: "remove", value: function remove(object) { if (object.isSkinnedMesh) { this._removeMesh(object); } else if (object.isCamera) { this._clearCamera(object); } else if (object.type === 'Audio') { this._clearAudio(object); } else { throw new Error('THREE.MMDAnimationHelper.remove: ' + 'accepts only ' + 'THREE.SkinnedMesh or ' + 'THREE.Camera or ' + 'THREE.Audio instance.'); } if (this.configuration.sync) this._syncDuration(); return this; } /** * Updates the animation. * * @param {Number} delta * @return {MMDAnimationHelper} */ }, { key: "update", value: function update(delta) { if (this.audioManager !== null) this.audioManager.control(delta); for (var i = 0; i < this.meshes.length; i++) { this._animateMesh(this.meshes[i], delta); } if (this.sharedPhysics) this._updateSharedPhysics(delta); if (this.camera !== null) this._animateCamera(this.camera, delta); return this; } /** * Changes the pose of SkinnedMesh as VPD specifies. * * @param {THREE.SkinnedMesh} mesh * @param {Object} vpd - VPD content parsed MMDParser * @param {Object} params - (optional) * @param {boolean} params.resetPose - Default is true. * @param {boolean} params.ik - Default is true. * @param {boolean} params.grant - Default is true. * @return {MMDAnimationHelper} */ }, { key: "pose", value: function pose(mesh, vpd) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (params.resetPose !== false) mesh.pose(); var bones = mesh.skeleton.bones; var boneParams = vpd.bones; var boneNameDictionary = {}; for (var i = 0, il = bones.length; i < il; i++) { boneNameDictionary[bones[i].name] = i; } var vector = new _three.Vector3(); var quaternion = new _three.Quaternion(); for (var _i = 0, _il = boneParams.length; _i < _il; _i++) { var boneParam = boneParams[_i]; var boneIndex = boneNameDictionary[boneParam.name]; if (boneIndex === undefined) continue; var bone = bones[boneIndex]; bone.position.add(vector.fromArray(boneParam.translation)); bone.quaternion.multiply(quaternion.fromArray(boneParam.quaternion)); } mesh.updateMatrixWorld(true); // PMX animation system special path if (this.configuration.pmxAnimation && mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx') { var sortedBonesData = this._sortBoneDataArray(mesh.geometry.userData.MMD.bones.slice()); var ikSolver = params.ik !== false ? this._createCCDIKSolver(mesh) : null; var grantSolver = params.grant !== false ? this.createGrantSolver(mesh) : null; this._animatePMXMesh(mesh, sortedBonesData, ikSolver, grantSolver); } else { if (params.ik !== false) { this._createCCDIKSolver(mesh).update(); } if (params.grant !== false) { this.createGrantSolver(mesh).update(); } } return this; } /** * Enabes/Disables an animation feature. * * @param {string} key * @param {boolean} enabled * @return {MMDAnimationHelper} */ }, { key: "enable", value: function enable(key, enabled) { if (this.enabled[key] === undefined) { throw new Error('THREE.MMDAnimationHelper.enable: ' + 'unknown key ' + key); } this.enabled[key] = enabled; if (key === 'physics') { for (var i = 0, il = this.meshes.length; i < il; i++) { this._optimizeIK(this.meshes[i], enabled); } } return this; } /** * Creates an GrantSolver instance. * * @param {THREE.SkinnedMesh} mesh * @return {GrantSolver} */ }, { key: "createGrantSolver", value: function createGrantSolver(mesh) { return new GrantSolver(mesh, mesh.geometry.userData.MMD.grants); } // private methods }, { key: "_addMesh", value: function _addMesh(mesh, params) { if (this.meshes.indexOf(mesh) >= 0) { throw new Error('THREE.MMDAnimationHelper._addMesh: ' + 'SkinnedMesh \'' + mesh.name + '\' has already been added.'); } this.meshes.push(mesh); this.objects.set(mesh, { looped: false }); this._setupMeshAnimation(mesh, params.animation); if (params.physics !== false) { this._setupMeshPhysics(mesh, params); } return this; } }, { key: "_setupCamera", value: function _setupCamera(camera, params) { if (this.camera === camera) { throw new Error('THREE.MMDAnimationHelper._setupCamera: ' + 'Camera \'' + camera.name + '\' has already been set.'); } if (this.camera) this.clearCamera(this.camera); this.camera = camera; camera.add(this.cameraTarget); this.objects.set(camera, {}); if (params.animation !== undefined) { this._setupCameraAnimation(camera, params.animation); } return this; } }, { key: "_setupAudio", value: function _setupAudio(audio, params) { if (this.audio === audio) { throw new Error('THREE.MMDAnimationHelper._setupAudio: ' + 'Audio \'' + audio.name + '\' has already been set.'); } if (this.audio) this.clearAudio(this.audio); this.audio = audio; this.audioManager = new AudioManager(audio, params); this.objects.set(this.audioManager, { duration: this.audioManager.duration }); return this; } }, { key: "_removeMesh", value: function _removeMesh(mesh) { var found = false; var writeIndex = 0; for (var i = 0, il = this.meshes.length; i < il; i++) { if (this.meshes[i] === mesh) { this.objects.delete(mesh); found = true; continue; } this.meshes[writeIndex++] = this.meshes[i]; } if (!found) { throw new Error('THREE.MMDAnimationHelper._removeMesh: ' + 'SkinnedMesh \'' + mesh.name + '\' has not been added yet.'); } this.meshes.length = writeIndex; return this; } }, { key: "_clearCamera", value: function _clearCamera(camera) { if (camera !== this.camera) { throw new Error('THREE.MMDAnimationHelper._clearCamera: ' + 'Camera \'' + camera.name + '\' has not been set yet.'); } this.camera.remove(this.cameraTarget); this.objects.delete(this.camera); this.camera = null; return this; } }, { key: "_clearAudio", value: function _clearAudio(audio) { if (audio !== this.audio) { throw new Error('THREE.MMDAnimationHelper._clearAudio: ' + 'Audio \'' + audio.name + '\' has not been set yet.'); } this.objects.delete(this.audioManager); this.audio = null; this.audioManager = null; return this; } }, { key: "_setupMeshAnimation", value: function _setupMeshAnimation(mesh, animation) { var objects = this.objects.get(mesh); if (animation !== undefined) { var animations = Array.isArray(animation) ? animation : [animation]; objects.mixer = new _three.AnimationMixer(mesh); for (var i = 0, il = animations.length; i < il; i++) { objects.mixer.clipAction(animations[i]).play(); } // TODO: find a workaround not to access ._clip looking like a private property objects.mixer.addEventListener('loop', function (event) { var tracks = event.action._clip.tracks; if (tracks.length > 0 && tracks[0].name.slice(0, 6) !== '.bones') return; objects.looped = true; }); } objects.ikSolver = this._createCCDIKSolver(mesh); objects.grantSolver = this.createGrantSolver(mesh); return this; } }, { key: "_setupCameraAnimation", value: function _setupCameraAnimation(camera, animation) { var animations = Array.isArray(animation) ? animation : [animation]; var objects = this.objects.get(camera); objects.mixer = new _three.AnimationMixer(camera); for (var i = 0, il = animations.length; i < il; i++) { objects.mixer.clipAction(animations[i]).play(); } } }, { key: "_setupMeshPhysics", value: function _setupMeshPhysics(mesh, params) { var objects = this.objects.get(mesh); // shared physics is experimental if (params.world === undefined && this.sharedPhysics) { var masterPhysics = this._getMasterPhysics(); if (masterPhysics !== null) world = masterPhysics.world; // eslint-disable-line no-undef } objects.physics = this._createMMDPhysics(mesh, params); if (objects.mixer && params.animationWarmup !== false) { this._animateMesh(mesh, 0); objects.physics.reset(); } objects.physics.warmup(params.warmup !== undefined ? params.warmup : 60); this._optimizeIK(mesh, true); } }, { key: "_animateMesh", value: function _animateMesh(mesh, delta) { var objects = this.objects.get(mesh); var mixer = objects.mixer; var ikSolver = objects.ikSolver; var grantSolver = objects.grantSolver; var physics = objects.physics; var looped = objects.looped; if (mixer && this.enabled.animation) { // alternate solution to save/restore bones but less performant? //mesh.pose(); //this._updatePropertyMixersBuffer( mesh ); this._restoreBones(mesh); mixer.update(delta); this._saveBones(mesh); // PMX animation system special path if (this.configuration.pmxAnimation && mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx') { if (!objects.sortedBonesData) objects.sortedBonesData = this._sortBoneDataArray(mesh.geometry.userData.MMD.bones.slice()); this._animatePMXMesh(mesh, objects.sortedBonesData, ikSolver && this.enabled.ik ? ikSolver : null, grantSolver && this.enabled.grant ? grantSolver : null); } else { if (ikSolver && this.enabled.ik) { mesh.updateMatrixWorld(true); ikSolver.update(); } if (grantSolver && this.enabled.grant) { grantSolver.update(); } } } if (looped === true && this.enabled.physics) { if (physics && this.configuration.resetPhysicsOnLoop) physics.reset(); objects.looped = false; } if (physics && this.enabled.physics && !this.sharedPhysics) { this.onBeforePhysics(mesh); physics.update(delta); } } // Sort bones in order by 1. transformationClass and 2. bone index. // In PMX animation system, bone transformations should be processed // in this order. }, { key: "_sortBoneDataArray", value: function _sortBoneDataArray(boneDataArray) { return boneDataArray.sort(function (a, b) { if (a.transformationClass !== b.transformationClass) { return a.transformationClass - b.transformationClass; } else { return a.index - b.index; } }); } // PMX Animation system is a bit too complex and doesn't great match to // Three.js Animation system. This method attempts to simulate it as much as // possible but doesn't perfectly simulate. // This method is more costly than the regular one so // you are recommended to set constructor parameter "pmxAnimation: true" // only if your PMX model animation doesn't work well. // If you need better method you would be required to write your own. }, { key: "_animatePMXMesh", value: function _animatePMXMesh(mesh, sortedBonesData, ikSolver, grantSolver) { _quaternionIndex = 0; _grantResultMap.clear(); for (var i = 0, il = sortedBonesData.length; i < il; i++) { updateOne(mesh, sortedBonesData[i].index, ikSolver, grantSolver); } mesh.updateMatrixWorld(true); return this; } }, { key: "_animateCamera", value: function _animateCamera(camera, delta) { var mixer = this.objects.get(camera).mixer; if (mixer && this.enabled.cameraAnimation) { mixer.update(delta); camera.updateProjectionMatrix(); camera.up.set(0, 1, 0); camera.up.applyQuaternion(camera.quaternion); camera.lookAt(this.cameraTarget.position); } } }, { key: "_optimizeIK", value: function _optimizeIK(mesh, physicsEnabled) { var iks = mesh.geometry.userData.MMD.iks; var bones = mesh.geometry.userData.MMD.bones; for (var i = 0, il = iks.length; i < il; i++) { var ik = iks[i]; var links = ik.links; for (var j = 0, jl = links.length; j < jl; j++) { var link = links[j]; if (physicsEnabled === true) { // disable IK of the bone the corresponding rigidBody type of which is 1 or 2 // because its rotation will be overriden by physics link.enabled = bones[link.index].rigidBodyType > 0 ? false : true; } else { link.enabled = true; } } } } }, { key: "_createCCDIKSolver", value: function _createCCDIKSolver(mesh) { if (_CCDIKSolver.CCDIKSolver === undefined) { throw new Error('THREE.MMDAnimationHelper: Import CCDIKSolver.'); } return new _CCDIKSolver.CCDIKSolver(mesh, mesh.geometry.userData.MMD.iks); } }, { key: "_createMMDPhysics", value: function _createMMDPhysics(mesh, params) { if (_MMDPhysics.MMDPhysics === undefined) { throw new Error('THREE.MMDPhysics: Import MMDPhysics.'); } return new _MMDPhysics.MMDPhysics(mesh, mesh.geometry.userData.MMD.rigidBodies, mesh.geometry.userData.MMD.constraints, params); } /* * Detects the longest duration and then sets it to them to sync. * TODO: Not to access private properties ( ._actions and ._clip ) */ }, { key: "_syncDuration", value: function _syncDuration() { var max = 0.0; var objects = this.objects; var meshes = this.meshes; var camera = this.camera; var audioManager = this.audioManager; // get the longest duration for (var i = 0, il = meshes.length; i < il; i++) { var mixer = this.objects.get(meshes[i]).mixer; if (mixer === undefined) continue; for (var j = 0; j < mixer._actions.length; j++) { var clip = mixer._actions[j]._clip; if (!objects.has(clip)) { objects.set(clip, { duration: clip.duration }); } max = Math.max(max, objects.get(clip).duration); } } if (camera !== null) { var _mixer = this.objects.get(camera).mixer; if (_mixer !== undefined) { for (var _i2 = 0, _il2 = _mixer._actions.length; _i2 < _il2; _i2++) { var _clip = _mixer._actions[_i2]._clip; if (!objects.has(_clip)) { objects.set(_clip, { duration: _clip.duration }); } max = Math.max(max, objects.get(_clip).duration); } } } if (audioManager !== null) { max = Math.max(max, objects.get(audioManager).duration); } max += this.configuration.afterglow; // update the duration for (var _i3 = 0, _il3 = this.meshes.length; _i3 < _il3; _i3++) { var _mixer2 = this.objects.get(this.meshes[_i3]).mixer; if (_mixer2 === undefined) continue; for (var _j = 0, jl = _mixer2._actions.length; _j < jl; _j++) { _mixer2._actions[_j]._clip.duration = max; } } if (camera !== null) { var _mixer3 = this.objects.get(camera).mixer; if (_mixer3 !== undefined) { for (var _i4 = 0, _il4 = _mixer3._actions.length; _i4 < _il4; _i4++) { _mixer3._actions[_i4]._clip.duration = max; } } } if (audioManager !== null) { audioManager.duration = max; } } // workaround }, { key: "_updatePropertyMixersBuffer", value: function _updatePropertyMixersBuffer(mesh) { var mixer = this.objects.get(mesh).mixer; var propertyMixers = mixer._bindings; var accuIndex = mixer._accuIndex; for (var i = 0, il = propertyMixers.length; i < il; i++) { var propertyMixer = propertyMixers[i]; var buffer = propertyMixer.buffer; var stride = propertyMixer.valueSize; var offset = (accuIndex + 1) * stride; propertyMixer.binding.getValue(buffer, offset); } } /* * Avoiding these two issues by restore/save bones before/after mixer animation. * * 1. PropertyMixer used by AnimationMixer holds cache value in .buffer. * Calculating IK, Grant, and Physics after mixer animation can break * the cache coherency. * * 2. Applying Grant two or more times without reset the posing breaks model. */ }, { key: "_saveBones", value: function _saveBones(mesh) { var objects = this.objects.get(mesh); var bones = mesh.skeleton.bones; var backupBones = objects.backupBones; if (backupBones === undefined) { backupBones = new Float32Array(bones.length * 7); objects.backupBones = backupBones; } for (var i = 0, il = bones.length; i < il; i++) { var bone = bones[i]; bone.position.toArray(backupBones, i * 7); bone.quaternion.toArray(backupBones, i * 7 + 3); } } }, { key: "_restoreBones", value: function _restoreBones(mesh) { var objects = this.objects.get(mesh); var backupBones = objects.backupBones; if (backupBones === undefined) return; var bones = mesh.skeleton.bones; for (var i = 0, il = bones.length; i < il; i++) { var bone = bones[i]; bone.position.fromArray(backupBones, i * 7); bone.quaternion.fromArray(backupBones, i * 7 + 3); } } // experimental }, { key: "_getMasterPhysics", value: function _getMasterPhysics() { if (this.masterPhysics !== null) return this.masterPhysics; for (var i = 0, il = this.meshes.length; i < il; i++) { var physics = this.meshes[i].physics; if (physics !== undefined && physics !== null) { this.masterPhysics = physics; return this.masterPhysics; } } return null; } }, { key: "_updateSharedPhysics", value: function _updateSharedPhysics(delta) { if (this.meshes.length === 0 || !this.enabled.physics || !this.sharedPhysics) return; var physics = this._getMasterPhysics(); if (physics === null) return; for (var i = 0, il = this.meshes.length; i < il; i++) { var p = this.meshes[i].physics; if (p !== null && p !== undefined) { p.updateRigidBodies(); } } physics.stepSimulation(delta); for (var _i5 = 0, _il5 = this.meshes.length; _i5 < _il5; _i5++) { var _p = this.meshes[_i5].physics; if (_p !== null && _p !== undefined) { _p.updateBones(); } } } }]); return MMDAnimationHelper; }(); // Keep working quaternions for less GC _exports.MMDAnimationHelper = MMDAnimationHelper; var _quaternions = []; var _quaternionIndex = 0; function getQuaternion() { if (_quaternionIndex >= _quaternions.length) { _quaternions.push(new _three.Quaternion()); } return _quaternions[_quaternionIndex++]; } // Save rotation whose grant and IK are already applied // used by grant children var _grantResultMap = new Map(); function updateOne(mesh, boneIndex, ikSolver, grantSolver) { var bones = mesh.skeleton.bones; var bonesData = mesh.geometry.userData.MMD.bones; var boneData = bonesData[boneIndex]; var bone = bones[boneIndex]; // Return if already updated by being referred as a grant parent. if (_grantResultMap.has(boneIndex)) return; var quaternion = getQuaternion(); // Initialize grant result here to prevent infinite loop. // If it's referred before updating with actual result later // result without applyting IK or grant is gotten // but better than composing of infinite loop. _grantResultMap.set(boneIndex, quaternion.copy(bone.quaternion)); // @TODO: Support global grant and grant position if (grantSolver && boneData.grant && !boneData.grant.isLocal && boneData.grant.affectRotation) { var parentIndex = boneData.grant.parentIndex; var ratio = boneData.grant.ratio; if (!_grantResultMap.has(parentIndex)) { updateOne(mesh, parentIndex, ikSolver, grantSolver); } grantSolver.addGrantRotation(bone, _grantResultMap.get(parentIndex), ratio); } if (ikSolver && boneData.ik) { // @TODO: Updating world matrices every time solving an IK bone is // costly. Optimize if possible. mesh.updateMatrixWorld(true); ikSolver.updateOne(boneData.ik); // No confident, but it seems the grant results with ik links should be updated? var links = boneData.ik.links; for (var i = 0, il = links.length; i < il; i++) { var link = links[i]; if (link.enabled === false) continue; var linkIndex = link.index; if (_grantResultMap.has(linkIndex)) { _grantResultMap.set(linkIndex, _grantResultMap.get(linkIndex).copy(bones[linkIndex].quaternion)); } } } // Update with the actual result here quaternion.copy(bone.quaternion); } // var AudioManager = /*#__PURE__*/function () { /** * @param {THREE.Audio} audio * @param {Object} params - (optional) * @param {Nuumber} params.delayTime */ function AudioManager(audio) { var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, AudioManager); this.audio = audio; this.elapsedTime = 0.0; this.currentTime = 0.0; this.delayTime = params.delayTime !== undefined ? params.delayTime : 0.0; this.audioDuration = this.audio.buffer.duration; this.duration = this.audioDuration + this.delayTime; } /** * @param {Number} delta * @return {AudioManager} */ _createClass(AudioManager, [{ key: "control", value: function control(delta) { this.elapsed += delta; this.currentTime += delta; if (this._shouldStopAudio()) this.audio.stop(); if (this._shouldStartAudio()) this.audio.play(); return this; } // private methods }, { key: "_shouldStartAudio", value: function _shouldStartAudio() { if (this.audio.isPlaying) return false; while (this.currentTime >= this.duration) { this.currentTime -= this.duration; } if (this.currentTime < this.delayTime) return false; // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration if (this.currentTime - this.delayTime > this.audioDuration) return false; return true; } }, { key: "_shouldStopAudio", value: function _shouldStopAudio() { return this.audio.isPlaying && this.currentTime >= this.duration; } }]); return AudioManager; }(); var _q = new _three.Quaternion(); /** * Solver for Grant (Fuyo in Japanese. I just google translated because * Fuyo may be MMD specific term and may not be common word in 3D CG terms.) * Grant propagates a bone's transform to other bones transforms even if * they are not children. * @param {THREE.SkinnedMesh} mesh * @param {Array} grants */ var GrantSolver = /*#__PURE__*/function () { function GrantSolver(mesh) { var grants = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; _classCallCheck(this, GrantSolver); this.mesh = mesh; this.grants = grants; } /** * Solve all the grant bones * @return {GrantSolver} */ _createClass(GrantSolver, [{ key: "update", value: function update() { var grants = this.grants; for (var i = 0, il = grants.length; i < il; i++) { this.updateOne(grants[i]); } return this; } /** * Solve a grant bone * @param {Object} grant - grant parameter * @return {GrantSolver} */ }, { key: "updateOne", value: function updateOne(grant) { var bones = this.mesh.skeleton.bones; var bone = bones[grant.index]; var parentBone = bones[grant.parentIndex]; if (grant.isLocal) { // TODO: implement if (grant.affectPosition) {} // TODO: implement if (grant.affectRotation) {} } else { // TODO: implement if (grant.affectPosition) {} if (grant.affectRotation) { this.addGrantRotation(bone, parentBone.quaternion, grant.ratio); } } return this; } }, { key: "addGrantRotation", value: function addGrantRotation(bone, q, ratio) { _q.set(0, 0, 0, 1); _q.slerp(q, ratio); bone.quaternion.multiply(_q); return this; } }]); return GrantSolver; }(); });