Home Reference Source Repository

js/base/DH3DObject.js

'use strict'

import Vector3 from './Vector3'
import Vector4 from './Vector4'
import DHEvent from './DHEvent'
import Model from './Model'
import ModelBank from './ModelBank'
import Motion from './Motion'
import MotionBank from './MotionBank'

/**
 * DH3DObject basic class
 * @access public
 */
export default class DH3DObject {
  /**
   * constructor
   * @access public
   * @constructor
   */
  constructor() {
    this._model = null
    this._motion = null
    this._renderer = null
    this._animator = null
    this._eventArray = [] // FIXME: not use
    this._motionEventArray = []

    this._moveCallbacks = []

    this._animationTime = 0.0
    this._animationFrame = 0.0
    this._animationSpeed = 1.0
    this._animating = false
    this._loop = false

    this._force = new Vector3(0, 0, 0)
    this._speed = new Vector3(0, 0, 0)
    this._position = new Vector3(0, 0, 0)
    this._direction = 0
    this._maxSpeed = 10.0

    this._state = null

    this._motionStep = 0.0
    this._motionCount = 0.0

    // motion blending
    this._motionBlendCount = 0.0
    this._motionBlendStep = 0.0

    this._autoDirection = false
    this._mirror = false
    this._reflectionMode = false
  }

  /**
   * set callback function for each move
   * @access public
   * @param {Function} func - callback which is called each frame
   * @return {number} 0: success, -1: failure (func is not function)
   */
  addMoveCallback(func) {
    if(typeof func !== 'function'){
      return -1
    }

    this._moveCallbacks.push(func)
    return 0
  }

  /**
   * remove callback function for each move
   * @access public
   * @param {Function} func - callback which is called each frame
   * @return {number} 0: success, -1: failure (func is not registered)
   */
  removeMoveCallback(func){
    const i = this._moveCallbacks.indexOf(func)
    if(i < 0){
      return -1
    }

    this._moveCallbacks.splice(i, 1)
    return 0
  }

  /**
   * move object
   * @access public
   * @param {float} elapsedTime - elapsed time (seconds) from previous frame
   * @returns {void}
   */
  move(elapsedTime) {
    // FIXME: params
    const friction = 10.0 * elapsedTime

    if(friction < 1.0){
      this._speed.mulAdd(this._speed, this._speed, -friction)
    }else{
      this._speed.setValue(0, 0, 0)
    }
    
    this._speed.mulAdd(this._speed, this._force, 5.0 * elapsedTime)
    const speedVal = this._speed.length()
    if(speedVal > this._maxSpeed){
      this._speed.mul(this._speed, this._maxSpeed / speedVal)
    }

    this._position.mulAdd(this._position, this._speed, elapsedTime)

    const myObj = this
    this._moveCallbacks.forEach( (func) => {
      func(myObj, elapsedTime)
    })

    this._model.rootBone.offset.setValue(this._position)

    if(this._autoDirection){
      const axis = new Vector3(0.0, 1.0, 0.0)
      if(this._force.z > 0.001 || this._force.z < -0.001){
        this._direction = Math.atan(this._force.x / this._force.z)
        if(this._force.z < 0){
          this._direction += Math.PI
        }
      }else if(this._force.x > 0.001){
        this._direction = Math.PI * 0.5
      }else if(this._force.x < -0.001){
        this._direction = Math.PI * -0.5
      }
      this._model.rootBone.rotate.createAxis(axis, this._direction)
    }
  }

  /**
   * get Model object
   * @access public
   * @returns {Model} - model object
   */
  getModel() {
    return this._model
  }

  /**
   * set Model object
   * @access public
   * @param {Model} model - Model object. Set null to delete object
   * @returns {void}
   */
  setModel(model = null) {
    if(this._model){
      this._model.destroy()
    }

    if(model === null){
      return false
    }

    if(model instanceof Model){
      if(this._renderer && model.renderer !== this._renderer){
        return ModelBank.getModelForRenderer(model.hashName, this._renderer)
          .then((m) => { this._model = m })
          .catch((error) => { console.error(`model load error: ${error}`) })
      }

      this._model = model
      return true
    }

    return ModelBank.getModel(model).then((m) => this.setModel(m))
  }

  /**
   * get Motion object
   * @access public
   * @returns {Motion} - Motion object
   */
  getMotion() {
    return this._motion
  }

  /**
   * set Motion object
   * @access public
   * @param {Motion} motion - Motion object. Set null to delete object
   * @returns {void}
   */
  setMotion(motion = null) {
    if(this._motion){
      this._motion.destroy()
    }

    if(motion === null){
      return false
    }

    if(motion instanceof Motion){
      this._motion = motion
      return true
    }

    return MotionBank.getMotion(motion).then((m) => this.setMotion(m))
  }

  /**
   * set Motion object to blend with current motion
   * @access public
   * @param {Motion} motion - Motion object to blend
   * @param {float} blendCount - number of frames to complete transition
   * @returns {void}
   */
  setMotionWithBlending(motion, blendCount) {
    this._motion = motion
    this._motionBlendCount = 1.0
    this._motionBlendStep = 1.0 / blendCount
    this._model.rootBone.setBlendValueRecursive()
  }

  /**
   * get Renderer object
   * @access public
   * @returns {Renderer} - Renderer object
   */
  getRenderer() {
    return this._renderer
  }

  /**
   * set Renderer object
   * @access public
   * @param {Renderer} renderer - Renderer object
   * @returns {void}
   */
  setRenderer(renderer) {
    this._renderer = renderer

    if(this._model && this._model.renderer !== renderer){
      return ModelBank.getModelForRenderer(this._model.hashName, renderer)
        .then((m) => { this._model = m })
        .catch((error) => { console.error(`model load error: ${error}`) })
    }

    return true
  }

  /**
   * get animating state
   * @access public
   * @returns {boolean} - true: enable animation, false: disable animation
   */
  getAnimating() {
    return this._animating
  }

  /**
   * set animating state
   * @access public
   * @param {boolean} animating - true: enable animation, false: disable animation
   * @returns {void}
   */
  setAnimating(animating = true) {
    if(animating){
      this._animating = true
    }else{
      this._animating = false
    }
  }

  /**
   * get loop state
   * @access public
   * @returns {boolean} - true: enable loop, false: disable loop
   */
  getLoop() {
    return this._loop
  }

  /**
   * set loop state
   * @access public
   * @param {boolean} loop - true: enable loop, false: disable loop
   * @returns {void}
   */
  setLoop(loop = true) {
    if(loop){
      this._loop = true
    }else{
      this._loop = false
    }
  }

  /**
   * get Animator object
   * @access public
   * @returns {Animator} - Animator object
   */
  getAnimator() {
    return this._animator
  }

  /**
   * set Animator object
   * @access public
   * @param {Animator} animator - Animator object
   * @returns {void}
   */
  setAnimator(animator) {
    this._animator = animator
  }

  /**
   * get animation time
   * @access public
   * @returns {float} - animation time (seconds)
   */
  getAnimationTime() {
    return this._animationTime
  }

  /**
   * set animation time
   * @access public
   * @param {float} time - animation time (seconds)
   * @returns {void}
   */
  setAnimationTime(time) {
    this._animationTime = time
  }

  /**
   * get animation speed
   * @access public
   * @returns {float} - animation speed: normal speed is 1.0
   */
  getAnimationSpeed() {
    return this._animationSpeed
  }

  /**
   * set animation speed
   * @access public
   * @param {float} speed - animation speed: normal speed is 1.0
   * @returns {void}
   */
  setAnimationSpeed(speed) {
    this._animationSpeed = speed
  }

  /**
   * get object position
   * @access public
   * @returns {Vector3} - object position
   */
  getPosition() {
    return this._position
  }

  /**
   * set object position
   * @access public
   * @param {Vector3|float} x - object position (Vector3 object or X value)
   * @param {float} y - Y value
   * @param {float} z - Z value
   * @returns {void}
   */
  setPosition(x, y, z) {
    if(x instanceof Vector3){
      this._position.x = x.x
      this._position.y = x.y
      this._position.z = x.z
    }else{
      this._position.x = x
      this._position.y = y
      this._position.z = z
    }
  }

  /**
   * get object speed
   * @access public
   * @returns {Vector3} - object speed
   */
  getSpeed() {
    return this._speed
  }

  /**
   * set object speed
   * @access public
   * @param {Vector3|float} x - object speed (Vector3 object or X value)
   * @param {float} y - Y value of speed
   * @param {float} z - Z value of speed
   * @returns {void}
   */
  setSpeed(x, y, z) {
    if(x instanceof Vector3){
      this._speed.x = x.x
      this._speed.y = x.y
      this._speed.z = x.z
    }else{
      this._speed.x = x
      this._speed.y = y
      this._speed.z = z
    }
  }

  /**
   * get quaternion for rotation
   * @access public
   * @returns {Vector4} - quaternion
   */
  getRotate() {
    return this._model.rootBone.rotate
  }

  /**
   * set quaternion for rotation
   * @access public
   * @param {Vector4|float} x - quaternion (Vector4 or X value)
   * @param {float} y - Y value
   * @param {float} z - Z value
   * @param {float} w - W value
   * @returns {void}
   */
  setRotate(x, y, z, w) {
    const rot = this._model.rootBone.rotate
    if(x instanceof Vector4){
      rot.x = x.x
      rot.y = x.y
      rot.z = x.z
      rot.w = x.w
      rot.normalize()
    }else{
      rot.x = x
      rot.y = y
      rot.z = z
      rot.w = w
      rot.normalize()
    }
  }

  setRotateAxis(axis, rotAngle) {
    this._model.rootBone.rotate.createAxis(axis, rotAngle)
  }

  setScale(x, y, z) {
    const scale = this._model.rootBone.scale
    if(y === undefined && z === undefined){
      scale.x = x
      scale.y = x
      scale.z = x
    }else{
      scale.x = x
      scale.y = y
      scale.z = z
    }
  }

  getAutoDirection() {
    return this._autoDirection
  }

  setAutoDirection(autoDirection = true) {
    if(autoDirection){
      this._autoDirection = true
    }else{
      this._autoDirection = false
    }
  }

  getMirror() {
    return this._mirror
  }

  setMirror(flag) {
    this._mirror = flag
  }

  getReflectionMode() {
    return this._reflectionMode
  }

  setReflectionMode(flag) {
    this._reflectionMode = flag
  }

  updateMaterial() {
    this._model.materialArray.forEach( (mat) => {
      mat.clearCache()
    })
  }

  getSkinArray() {
    if(this._model){
      return this._model.getSkinArray()
    }
    return null
  }

  getDynamicSkinArray() {
    if(this._model){
      return this._model.getDynamicSkinArray()
    }
    return null
  }

  getNumElements() {
    if(this._model){
      return this._model.indexArray.length / 3
    }
    return 0
  }

  // FIXME: implementation
  getTexture() {
    if(this._model){
      return null
    }
    return null
  }

  animate(elapsedTime) {
    const animationTimeBefore = this._animationTime
    if(this._animator){
      this._animator.animate(this, elapsedTime)
    }else{
      // ボーンの行列更新のみ行う
      this._model.rootBone.updateMatrixRecursive()
    }
    const animationTimeAfter = this._animationTime

    const obj = this
    this._motionEventArray.forEach( (me) => {
      if(animationTimeBefore < me.time && me.time <= animationTimeAfter){
        if(!me.state || me.state === obj._state){
          me.start()
        }
      }
    })
  }

  render() {
    if(this._renderer)
      this._renderer.render(this)
  }

  renderMirror(reflectionObjectArray) {
    if(this._renderer){
      this._renderer.renderMirror(this, reflectionObjectArray)
    }
  }

  addMotionCallback(func, time, state) {
    const motionEvent = new DHEvent()
    motionEvent.time = time
    motionEvent.state = state
    motionEvent.setEventCallback(func)

    this._motionEventArray.push(motionEvent)
  }

  removeMotionCallback(func, time, state) {
    const arr = this._motionEventArray
    let target = null
    arr.forEach( (me) => {
      if(me.time === time && me.state === state
         && me.getEventCallback() === func){
        target = me
        me.delete()
      }
    })
    if(target){
      this._motionEventArray = arr.filter((ev) => (ev !== target))
    }
  }

  clearMotionCallback() {
    const arr = this._motionEventArray
    arr.forEach( (me) => {
      me.delete()
    })
    arr.length = 0
  }

  getDirection() {
    return this._direction
  }

  setDirection(direction) {
    this._direction = direction
  }

  getMaxSpeed() {
    return this._maxSpeed
  }

  setMaxSpeed(maxSpeed) {
    this._maxSpeed = maxSpeed
  }

  getState() {
    return this._state
  }

  setState(state) {
    this._state = state
  }
}