Home Reference Source Repository

js/base/IK.js

'use strict'

import Vector3 from './Vector3'
import Vector4 from './Vector4'
import Matrix from './Matrix'

import ObjectAssign from '../etc/ObjectAssign'

/**
 * IK class
 * @access public
 */
export default class IK {
  /**
   * constructor
   * @access public
   * @constructor
   */
  constructor() {
    this.boneList = []
    this.minAngleList = []
    this.maxAngleList = []
    this.targetBone = null
    this.effectBone = null
    this.iteration = 0
    this.weight = 0.0
    this.linkNo = null

    this._angle = new Vector3()
    this._orgTargetPos = new Vector3()
    this._rotAxis = new Vector3()
    this._rotQuat = new Vector4()
    this._inverseMat = new Matrix()
    this._diff = new Vector3()
    this._effectPos = new Vector3()
    this._targetPos = new Vector3()
  }

  /**
   * clone IK object
   * @access public
   * @returns {IK} - copied object
   */
  clone() {
    //const newIK = Object.assign(new IK(), this)
    const newIK = ObjectAssign(new IK(), this)

    newIK._angle        = this._angle.clone()
    newIK._orgTargetPos = this._orgTargetPos.clone()
    newIK._rotAxis      = this._rotAxis.clone()
    newIK._rotQuat      = this._rotQuat.clone()
    newIK._inverseMat   = this._inverseMat.clone()
    newIK._diff         = this._diff.clone()
    newIK._effectPos    = this._effectPos.clone()
    newIK._targetPos    = this._targetPos.clone()

    return newIK
  }

  /**
   * limit angle of joint
   * @access public
   * @param {float} limitMinAngle - minimum angle of joint
   * @param {float} limitMaxAngle - maximum angle of joint
   * @param {Vector4} quat - Quaternion
   * @returns {void}
   */
  limitAngle(limitMinAngle, limitMaxAngle, quat) {
    const angle = this._angle

    angle.quaternionToEuler(quat)

    if(angle.x < limitMinAngle.x)
      angle.x = limitMinAngle.x
    else if(angle.x > limitMaxAngle.x)
      angle.x = limitMaxAngle.x

    if(angle.y < limitMinAngle.y)
      angle.y = limitMinAngle.y
    else if(angle.y > limitMaxAngle.y)
      angle.y = limitMaxAngle.y

    if(angle.z < limitMinAngle.z)
      angle.z = limitMinAngle.z
    else if(angle.z > limitMaxAngle.z)
      angle.z = limitMaxAngle.z

    quat.eulerToQuaternion(angle)
  }

  /**
   * update
   * @access public
   * @returns {void}
   */
  update() {
    const zeroThreshold = 0.0001
    const targetMat = this.targetBone.localMatrix
    const orgTargetPos = this._orgTargetPos
    orgTargetPos.setValue(targetMat.m41, targetMat.m42, targetMat.m43)
    const rotAxis = this._rotAxis
    const rotQuat = this._rotQuat
    const inverseMat = this._inverseMat
    const diff = this._diff
    const effectPos = this._effectPos
    const targetPos = this._targetPos

    for(let i=this.boneList.length-1; i>=0; i--){
      this.boneList[i].updateMatrix()
    }
    this.effectBone.updateMatrix()

    // FIXME: 要再考
    for(let calcCount=0; calcCount<this.iteration; calcCount++){
      for(let linkIndex=0; linkIndex<this.boneList.length; linkIndex++){
        const linkedBone = this.boneList[linkIndex]
        const effectMat = this.effectBone.localMatrix
        effectPos.setValue(effectMat.m41, effectMat.m42, effectMat.m43)

        inverseMat.inverseMatrix(linkedBone.localMatrix)
        effectPos.transform(effectPos, inverseMat)
        targetPos.transform(orgTargetPos, inverseMat)

        effectPos.normalize()
        targetPos.normalize()

        diff.sub(effectPos, targetPos)
        if(diff.length() < zeroThreshold){
          return
        }

        let eDotT = effectPos.dot(targetPos)
        if(eDotT >  1.0) eDotT =  1.0
        if(eDotT < -1.0) eDotT = -1.0

        let rotAngle = Math.acos(eDotT)
        if(rotAngle > this.weight * (linkIndex + 1) * 4){
          rotAngle = this.weight * (linkIndex + 1) * 4
        }

        rotAxis.cross(effectPos, targetPos)
        if(rotAxis.length() < zeroThreshold){
          break
        }
        rotAxis.normalize()

        // limit angle
        
        rotQuat.createAxis(rotAxis, rotAngle)
        rotQuat.normalize()
        /*
        if(this.minAngleList[linkIndex]){
          this.limitAngle(this.minAngleList[linkIndex], this.maxAngleList[linkIndex], rotQuat)
        }
        */

        linkedBone.rotate.cross(linkedBone.rotate, rotQuat)
        linkedBone.rotate.normalize()

        for(let i=linkIndex; i>=0; i--){
          this.boneList[i].updateMatrix()
        }
        this.effectBone.updateMatrix()
      }
    }
  }
}