Home Reference Source Repository

js/mmd/VMDReader.js

'use strict'

import MotionReader from '../base/MotionReader'
import BinaryReader from '../util/BinaryReader'
import FaceMotion from './FaceMotion'
import KeyFrame from '../base/KeyFrame'
import CameraKeyFrame from '../base/CameraKeyFrame'
import LightKeyFrame from '../base/LightKeyFrame'
import ShadowKeyFrame from '../base/ShadowKeyFrame'
import VMDMotion from './VMDMotion'
import Vector3 from '../base/Vector3'
import Vector4 from '../base/Vector4'
import IKFrame from './IKFrame'
import MotionBank from '../base/MotionBank'

/**
 * VMDReader class
 * @access public
 */
export default class VMDReader extends MotionReader {
  /**
   * constructor
   * @access public
   * @constructor
   */
  constructor() {
    super()
    this._binaryReader = null
    this._motion = null
    this._type = 'unknown'
  }

  readMotion(url) {
    if(!VMDReader.canRead(url))
      return false

    const obj = this
    const onload = () => { obj.readMotionProcess(url) }

    this._motion = new VMDMotion()

    const promise = new BinaryReader.open(url, false, 'sjis')
      .then((reader) => {
        this._binaryReader = reader
        return this.readMotionProcess(url)
      })
      .catch((err) => {
        console.error(`file (${url}) open error: ${err}`)
      })

    return promise
  }

  readMotionFromFile(file) {
    if(!VMDReader.canRead(file))
      return false

    this._motion = new VMDMotion()

    const promise = new BinaryReader.open(file, false, 'sjis')
      .then((reader) => {
        this._binaryReader = reader
        return this.readMotionProcess(null)
      })
      .catch((err) => {
        console.error(`file (${file.name}) open error: ${err}`)
      })

    return promise
  }

  readMotionProcess(url) {
    const result = this.readMotionSub(url)

    if(!result){
      if(this._motion.onerror){
        this._motion.onerror()
      }
      return Promise.reject('read motion error')
    }

    this._motion.loaded = true
    if(this._motion.onload){
      this._motion.onload()
    }

    if(this._motion.onloadend){
      this._motion.onloadend()
    }
    return Promise.resolve(this._motion)
  }

  readMotionSub(){
    this._motion.frameLength = 0

    this.readHeader()
    this.readFrames()
    this.readFace()

    if(!this._binaryReader.hasBytesAvailable())
      return this._motion

    this.readCamera()
    this.readLight()

    if(!this._binaryReader.hasBytesAvailable())
      return this._motion

    console.log(`data length: ${this._binaryReader.data.length}`)
    console.log(`position: ${this._binaryReader.position}`)
    this.readShadow()

    if(!this._binaryReader.hasBytesAvailable())
      return this._motion

    this.readVisibilityAndIK()

    return this._motion
  }

  readHeader() {
    const header = this._binaryReader.readString(30)
    if(header !== 'Vocaloid Motion Data 0002'){
      console.log(`VMD Format Error: ${header}`)
      //myAlert('VMD Format Error')
    }
    this._motion.name = this._binaryReader.readString(20)

    console.log(this._motion.name)
    if(this._motion.name === 'カメラ・照明'){
      console.log('camera/light motion')
      this._type = 'camera/light'
    }else{
      this._type = 'model'
    }
  }

  readFrames() {
    const frames = this._binaryReader.readUnsignedInt()
    const motionArray = this._motion.motionArray
    const bytesPerFrame = 111

    console.log(`bone frames: ${frames}`)

    if(frames === 0)
      return

    if(this._type !== 'model'){
      console.log('error: not model motion data has bone motion data')

      // skip data
      this._binaryReader.skipBytes(bytesPerFrame * frames)
      return
    }

    for(let i=0; i<frames; i++){
      const frame = new KeyFrame()
      const boneName = this._binaryReader.readString(15)

      frame.frameNo = this._binaryReader.readUnsignedInt()
      if(frame.frameNo > this._motion.frameLength)
        this._motion.frameLength = frame.frameNo

      frame.position = new Vector3(
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat(),
       -this._binaryReader.readFloat()
      )

      frame.rotate = new Vector4(
       -this._binaryReader.readFloat(),
       -this._binaryReader.readFloat(),
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat()
      )
      frame.rotate.normalize(frame.rotate)

      frame.interpolation = []
      for(let j=0; j<64; j++){
        frame.interpolation[j] = this._binaryReader.readUnsignedByte()
      }

      if(!motionArray.get(boneName)){
        motionArray.set(boneName, [])
      }
      motionArray.get(boneName).push(frame)
    }

    // sort each keyFrames in ascending order of frameNo
    motionArray.forEach( (keyFrames, key) => {
      keyFrames.sort( (a, b) => { return a.frameNo - b.frameNo } )
    })
  }

  readFace() {
    const frames = this._binaryReader.readUnsignedInt()
    const faceMotionArray = this._motion.faceMotionArray
    const bytesPerFrame = 23

    console.log(`face frames: ${frames}`)

    if(frames === 0)
      return

    if(this._type !== 'model'){
      console.log('error: not model motion data has face motion data')

      // skip data
      this._binaryReader.skipBytes(bytesPerFrame * frames)
      return
    }

    for(let i=0; i<frames; i++){
      const faceMotion = new FaceMotion()
      const faceName = this._binaryReader.readString(15)

      faceMotion.frameNo = this._binaryReader.readUnsignedInt()
      if(faceMotion.frameNo > this._motion.frameLength)
        this._motion.frameLength = faceMotion.frameNo

      faceMotion.factor = this._binaryReader.readFloat()

      if(!faceMotionArray.get(faceName)){
        faceMotionArray.set(faceName, [])
      }
      faceMotionArray.get(faceName).push(faceMotion)
    }

    // sort each keyFrames in ascending order of frameNo
    faceMotionArray.forEach( (keyFrames, key) => {
      keyFrames.sort( (a, b) => { return a.frameNo - b.frameNo } )
    })
  }

  readCamera() {
    const frames = this._binaryReader.readUnsignedInt()
    const motionArray = this._motion.motionArray
    const bytesPerFrame = 61

    console.log(`camera frame: ${frames}`)

    if(frames === 0)
      return

    if(this._type !== 'camera/light'){
      console.error('error: not camera motion has camera motion data')
      // skip data
      this._binaryReader.skipBytes(bytesPerFrame * frames)
      return
    }
    this._type = 'camera'

    for(let i=0; i<frames; i++){
      const frame = new CameraKeyFrame()

      frame.frameNo = this._binaryReader.readUnsignedInt()
      if(frame.frameNo > this._motion.frameLength)
        this._motion.frameLength = frame.frameNo

      frame.distance = this._binaryReader.readFloat()

      frame.position = new Vector3(
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat()
      )

      frame.rotate = new Vector3(
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat()
      )

      frame.interpolation = []
      for(let j=0; j<24; j++){
        frame.interpolation[j] = this._binaryReader.readUnsignedByte()
      }

      const angleDegree = this._binaryReader.readInt()
      //frame.angle = Math.PI * angleDegree / 180.0
      frame.angle = angleDegree

      const perspective = this._binaryReader.readByte()
      frame.perspective = (perspective === 0)

      motionArray.push(frame)
    }

    // sort motionArray in ascending order of frameNo
    motionArray.sort( (a, b) => {
      return a.frameNo - b.frameNo
    })
  }

  readLight() {
    const frames = this._binaryReader.readUnsignedInt()
    const motionArray = this._motion.motionArray
    const bytesPerFrame = 28

    console.log(`light frame: ${frames}`)

    if(frames === 0)
      return

    if(this._type !== 'camera/light'){
      console.error('error: not light motion has light motion data')

      // skip data
      this._binaryReader.skipBytes(bytesPerFrame * frames)
      return
    }
    this._type = 'light'
    
    for(let i=0; i<frames; i++){
      const frame = new LightKeyFrame()

      frame.frameNo = this._binaryReader.readUnsignedInt()
      if(frame.frameNo > this._motion.frameLength)
        this._motion.frameLength = frame.frameNo

      frame.color = new Vector3(
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat()
      )

      frame.position = new Vector3(
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat(),
        this._binaryReader.readFloat()
      )

      motionArray.push(frame)
    }

    // sort motionArray in ascending order of frameNo
    motionArray.sort( (a, b) => {
      return a.frameNo - b.frameNo
    })
  }

  readShadow() {
    const frames = this._binaryReader.readUnsignedInt()
    const shadowArray = this._motion.shadowArray
    const bytesPerFrame = 9

    console.log(`shadow frame: ${frames}`)

    if(frames === 0)
      return

    if(this._type !== 'model'){
      console.error('error: not model motion has shadow data')

      // skip data
      this._binaryReader.skipBytes(bytesPerFrame * frames)
      return
    }

    if(!this._binaryReader.hasBytesAvailable(bytesPerFrame * frames)){
      console.error('it seems to be bad format...')

      this._binaryReader.skipBytes(9 * frames)
      return
    }

    for(let i=0; i<frames; i++){
      const frame = new ShadowKeyFrame()

      frame.frameNo = this._binaryReader.readUnsignedInt()
      frame.mode = this._binaryReader.readUnsignedByte()
      frame.distance = this._binaryReader.readFloat()

      shadowArray.push(frame)
    }

    this._motion.shadowArray.sort( (a, b) => {
      return a.frameNo - b.frameNo
    })
  }

  readVisibilityAndIK() {
    const frames = this._binaryReader.readUnsignedInt()
    const ikArray = this._motion.ikArray

    console.log(`ik frame: ${frames}`)

    if(frames === 0)
      return

    if(this._type !== 'model'){
      console.error('error: not model motion has ik data')
      return
    }

    if(!this._binaryReader.hasBytesAvailable(9 * frames)){
      console.error('it seems to be bad format...')

      this._binaryReader.skipBytes(9 * frames)
      return
    }

    for(let i=0; i<frames; i++){
      const frame = new IKFrame()

      frame.frameNo = this._binaryReader.readUnsignedInt()
      frame.visible = this._binaryReader.readUnsignedByte()
      frame.ikNum = this._binaryReader.readUnsignedInt()
      frame.ikData = []

      const ikNum = frame.ikNum

      for(let j=0; j<ikNum; j++){
        const boneName = this._binaryReader.readString(20)
        const ikOn = this._binaryReader.readUnsignedByte()

        frame.ikData.push(new Map(boneName, ikOn))
      }
      ikArray.push(frame)
    }

    ikArray.sort( (a, b) => {
      return a.frameNo - b.frameNo
    })
  }
}

VMDReader.canRead = (file) => {
  let ext = ''
  if(file instanceof File){
    ext = file.name.substr(-4)
  }else if(typeof file === 'string' || file instanceof String){
    ext = file.substr(-4)
  }

  if(ext === '.vmd'){
    return true
  }

  return false
}


MotionBank.addMotionReader(VMDReader)