Home Reference Source Repository

js/mmd/MMDAnimator.js

'use strict'

import Animator from '../base/Animator'

/**
 * MMDAnimator class
 * @access public
 */
export default class MMDAnimator extends Animator {

  updateMotion(dhObject, elapsedTime) {
    const model = dhObject._model
    const motion = dhObject._motion
    const time = elapsedTime * dhObject._animationSpeed

    if(dhObject._animating){
      dhObject._animationTime += time
      const frameNo = dhObject._animationTime * motion.defaultFPS
      if(frameNo > motion.frameLength){
        if(dhObject._loop){
          dhObject._animationTime -= motion.frameLength / motion.defaultFPS
        }else{
          dhObject._animationTime = motion.frameLength / motion.defaultFPS
          dhObject._animating = false
        }
      }
      if(dhObject._motionBlendStep){
        dhObject._motionBlendCount -= dhObject._motionBlendStep * motion.defaultFPS * time
        if(dhObject._motionBlendCount < 0){
          dhObject._motionBlendCount = 0
          dhObject._motionBlendStep = 0
        }
      }
    }
    dhObject._animationFrame = dhObject._animationTime * motion.defaultFPS

    const animator = this
    motion.motionArray.forEach( (boneMotion, key) => {
      const bone = model.boneHash.get(key)
      if(!bone){
        return
      }

      animator.setBone(dhObject, boneMotion, bone)
    })

    if(model.faceArray.length > 0){
      model.faceArray[0].setFace(model)
      motion.faceMotionArray.forEach( (faceMotion, key) => {
        const face = model.faceHash.get(key)
        if(!face)
          return

        animator.setFace(dhObject, faceMotion, face)
      })
    }
  }

  setBone(dhObject, motion, bone) {
    if(!dhObject._motionNumCache)
      dhObject._motionNumCache = new Map()

    let frameNo = dhObject._motionNumCache.get(bone.name)
    const time = dhObject._animationFrame
    const motionLen = motion.length
    let key0 = 0
    let key1 = 0

    if(!frameNo)
      frameNo = 0

    if(motionLen <= frameNo)
      frameNo = motionLen - 1

    if(motion[frameNo].frameNo < time){
      for(; frameNo < motionLen; frameNo++){
        if(motion[frameNo].frameNo >= time)
          break
      }
      key0 = frameNo - 1
      key1 = frameNo
    }else{
      for(; frameNo >= 0; frameNo--){
        if(motion[frameNo].frameNo < time)
          break
      }
      key0 = frameNo
      key1 = frameNo + 1
    }
    if(key0 <= 0)         key0 = 0
    if(key1 >= motionLen) key1 = motionLen - 1

    // cache motion number
    dhObject._motionNumCache.set(bone.name, key0)

    const motion0 = motion[key0]
    const motion1 = motion[key1]
    const time0 = motion0.frameNo
    const time1 = motion1.frameNo

    const pos = bone.position
    const rot = bone.rotate
    if(time0 !== time1){
      const k = 127 * (time - time0) / (time1 - time0)
      pos.x = this.getBezierValue(motion1.interpolation[0],  motion1.interpolation[4],
                                  motion1.interpolation[8],  motion1.interpolation[12],
                                  motion0.position.x, motion1.position.x, k)
      pos.y = this.getBezierValue(motion1.interpolation[1],  motion1.interpolation[5],
                                  motion1.interpolation[9],  motion1.interpolation[13],
                                  motion0.position.y, motion1.position.y, k)
      pos.z = this.getBezierValue(motion1.interpolation[2],  motion1.interpolation[6],
                                  motion1.interpolation[10], motion1.interpolation[14],
                                  motion0.position.z, motion1.position.z, k)
      const r = this.getBezierValue(motion1.interpolation[3],  motion1.interpolation[7],
                                  motion1.interpolation[11], motion1.interpolation[15],
                                  0, 1, k)
      // DEBUG
      //rot.slerp(motion0.rotate, motion1.rotate, r)
      rot.lerp(motion0.rotate, motion1.rotate, r)
      rot.normalize()
    }else{
      pos.setValue(motion0.position)
      rot.setValue(motion0.rotate)
    }

    if(dhObject._motionBlendCount){
      pos.lerp(pos, bone.blendPosition, dhObject._motionBlendCount)
      rot.lerp(rot, bone.blendRotation, dhObject._motionBlendCount)
    }
  }

  getBezierValue(bx1, by1, bx2, by2, y0, y1, k) {
    let r = 0
    let val = 0
    let t0 = 0
    let t1 = 127 / 127.0
    let t = 63.5 / 127.0
    const nx1 = bx1 / 127.0
    const ny1 = by1 / 127.0
    const nx2 = bx2 / 127.0
    const ny2 = by2 / 127.0
    const nk = k / 127.0

    for(let i=0; i<8; i++){
      r = 1-t
      val = 3*t*r*(nx1*r + nx2*t) + t*t*t
      if(nk > val){
        t0 = t
      }else{
        t1 = t
      }
      t = (t0 + t1) / 2
    }
    r = 1-t
    val = (3*t*r*(ny1*r + ny2*t) + t*t*t)

    return (y0 + (y1 - y0) * val)
  }

  setFace(dhObject, faceMotion, face) {
    if(!dhObject._faceMotionNumCache)
      dhObject._faceMotionNumCache = new Map()

    let frameNo = dhObject._faceMotionNumCache.get(face.name)
    let time = dhObject._animationFrame
    const motionLen = faceMotion.length
    let key0 = 0
    let key1 = 0

    if(!frameNo)
      frameNo = 0

    if(time > faceMotion[motionLen-1].frameNo){
      time = faceMotion[motionLen-1].frameNo
    }

    if(faceMotion[frameNo].frameNo < time){
      for(; frameNo < motionLen; frameNo++){
        if(faceMotion[frameNo].frameNo >= time)
          break
      }
      key0 = frameNo - 1
      key1 = frameNo
    }else{
      for(; frameNo >= 0; frameNo--){
        if(faceMotion[frameNo].frameNo < time)
          break
      }
      key0 = frameNo
      key1 = frameNo + 1
    }
    if(key0 < 0)          key0 = 0
    if(key1 >= motionLen) key1 = motionLen - 1

    const motion0 = faceMotion[key0]
    const motion1 = faceMotion[key1]
    const time0 = motion0.frameNo
    const time1 = motion1.frameNo

    let rate = 0
    if(time0 !== time1){
      const k = (time1 - time) / (time1 - time0)
      rate = motion0.factor * k + motion1.factor * (1.0 - k)
    }else{
      rate = motion0.factor
    }
    face.blendFace(dhObject._model, rate)
  }
}