こちらは Maya Advent Calendar2018 12月11日の記事です。
だいぶ期日過ぎた投稿になりますが、まだ枠が空いておりましたので、僭越ながら投稿いたします。
まえがき
以前Twitter経由で「Mayaのノードで数学の演算を実現する」という記事を拝見いたしました。
この中でanimcurveノードを使った、疑似的な数学の演算手法が紹介されており、私自身も同様手法を用いてsoftIKのリグを実装しました。
正直自分の実装方法については、個人的には「こりゃ強引だよなぁ…」などと思って、自信が持てなかったのですが、同様のアプローチをされている方がいたということに、内心ほっとしたというか、勇気づけられたといいますか。
そこで、こちらの記事では私が取ったsoftIKの実装方法について、簡単に紹介したいと思います。
設計
softIKの一番のポイントは、
IKを操作すると…
- IKハンドルが、起点からある一定距離以内であれば、通常通りの挙動を取る
- ある一定距離を超えると、IKが伸びきる距離に近似する挙動を取る
ということ。
この「近似挙動をとる」って時点でlogとか三角関数とかのグラフを使いたいマンになるわけですが。
グラフ形状を計算すると、
途中までは
y=x
途中からある定数の直線
y=K
に近似するグラフになるわけです。
つまり、y=Xとy=Kの二つの接線を持つグラフ、となるわけです。
問題点
しかし、Mayaには残念ながらデフォルトで三角関数やlogを計算してくれる計算ノードがありません。
(melにはあるけど。)
Plugin等を作成して計算させてもよいのですが、どんな環境でもすんなり開けるようにしたかったので、Mayaデフォルトのノードで構築することにしました。さてどうしよう。
解決策
端的に考えて、
y=Xとy=Kの二つの接線を持つグラフ
という点を考えると、spline曲線が使えそうです。
そこで、animcurveノードで何とかできないか、模索してみました。
Autodeskのドキュメントによると、animcurveノードには以下の種類があります。
Name | Input Type | Output Type |
---|---|---|
animCurveTL | time | distance |
animCurveTA | time | angle |
animCurveTT | time | time |
animCurveTU | time | double |
animCurveUL | double | distance |
animCurveUA | double | angle |
animCurveUT | double | time |
animCurveUU | double | double |
上記の表から、input・output両方ともdoubleで受け付けているanimCurveUUノードが使えそうです。
(ドリブンキーで設定されるanimCurveノードがこれにあたりますね。)
補完がかかる範囲について少し整理すると、
こんな感じ。
上記の、「補完がかかる範囲」のところで、このanimCurveで代用した近似グラフを使ってやれば、何とかなりそうです。
実装
では、上記の設計を基に実装してみます。
scene構成
- 簡単なIK挙動での実装をテストするため、単純な2ボーンIK用のジョイント構造を用意。
- 距離の起点を取得するためのRootノードに各々リグノードを格納
- softIK補完がかかっていない、デフォルトの距離を取得するためのbaseTrnsノードを作成
- softIK補完をかけるノード、softTrnsを作成。
- baseTrnsとコントローラー、softTrnsをikHandleにコンストレイン接続
各種リグノードの作成
softIKの各種情報を格納するため、networkNodeを作成し、addAttrで必要なアトリビュートを追加。
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
import maya.cmds as mc
import maya.api.OpenMaya as om2
nt = mc.createNode('network',n='softIK_info')
mc.addAttr(nt,ln='ikMaxLength',at='float')
mc.addAttr(nt,ln='startLengthSoftCorrection',at='float')
name | 説明 |
---|---|
ikMaxLength | 距離の最大値を格納 |
startLengthSoftCorrection | softIK補完を開始する距離を格納 |
次に、IK設定時のbaseTrnsまでの距離を最大値として、MaxLengthを取得
とりあえず、softIK補完の開始距離を80%くらいからと仮定し、startLengthSoftCorrectionも設定しちゃいます。
#get max Distance
rootPoint = mc.xform('root',q=True,rp=True,ws=True,wd=True)
currentPoint = mc.xform('baseTrns',q=True,rp=True,ws=True,wd=True)
rootVector = om2.MVector(rootPoint)
currentVector = om2.MVector(currentPoint)
distanceVector = rootVector-currentVector
maxDistance = distanceVector.length()
softStartLength = maxDistance*0.8
mc.setAttr('%s.ikMaxLength'%nt,maxDistance)
mc.setAttr('%s.startLengthSoftCorrection'%nt,softStartLength)
便宜上、ここでは距離計算にopenMayaからvectorを持ってきて使っています。
vector便利。
さて、softIK補完の計算ノードを構築しましょう。
baseTrnsノードのMatrixから、rootからの距離をdistanceBetweenNodeで取得し、各種計算ノードで、補完スタート地点からどれくらい進んでいるかを計算します。
#culculate soft pct
distNode = mc.shadingNode('distanceBetween',au=True,n='currentLength')
mc.connectAttr('baseTrns.matrix','%s.inMatrix2'%distNode,f=True)
culLength = mc.shadingNode('plusMinusAverage',au=True,n='culLength')
mc.setAttr('%s.operation'%culLength,2)
mc.connectAttr('%s.distance'%distNode,'%s.input2D[0].input2Dx'%culLength)
mc.connectAttr('%s.startLengthSoftCorrection'%nt,'%s.input2D[1].input2Dx'%culLength)
mc.connectAttr('%s.ikMaxLength'%nt,'%s.input2D[0].input2Dy'%culLength)
mc.connectAttr('%s.startLengthSoftCorrection'%nt,'%s.input2D[1].input2Dy'%culLength)
culSoftPct = mc.shadingNode('multiplyDivide',au=True,n='culSoftPct')
mc.setAttr('%s.operation'%culSoftPct,2)
mc.connectAttr('%s.output2Dx'%culLength,'%s.input1X'%culSoftPct)
mc.connectAttr('%s.output2Dy'%culLength,'%s.input2X'%culSoftPct)
◇ここが混乱ポイントなのですが。
設計時の表でも示しましたが、全体の距離のうち、補完がかかる範囲は、「補完スタート地点」から「MaxLength」までです。 そのため、その範囲をどれくらい進んでいるのかを計算する必要があるわけです。
ここまでのノード構造は
こんな感じになります。
animCurveUUを使ったグラフ制御
さて、ここからcurveAnimUUノードを使って、softPctに対してsoftIK補完のグラフを作成します。
softIK補完の計算結果を格納するノードを作成し、そこにanimCurveUUノードを接続します。
#create animCurveNode
culSoftLength = mc.shadingNode('multDoubleLinear',au=True,n='culSoftLength')
cv = mc.createNode('animCurveUU',n='softCorrectionCurve')
mc.connectAttr('%s.outputX'%culSoftPct,'%s.input'%cv)
mc.connectAttr('%s.output'%cv,'%s.input1'%culSoftLength)
mc.connectAttr('%s.output2Dy'%culLength,'%s.input2'%culSoftLength)
※なお作成したculSoftLengthノードには、animCurveノードで計算したsoftIK補完後の数値が0~1で返されます。 そのため、元のスケールに戻すため、上記コードでは、補完がかかる範囲を何%進んだかを計算する際に使用した、分母の値を掛けています。
グラフカーブの形状を修正
ひとまず、y=xの形でグラフカーブを作成し、その時のindex0のkeyframeのtangentAngleを取得しておきます。
ここでは仮に、x=2の時にy=1に接する事にして、index1のkeyframeを1から2へ変更し、あらかじめ取得しておいたy=xのtangentAngleをinAngleに代入することによってy=xの接線を設定します。
mc.setDrivenKeyframe(culSoftLength,at=('input1'),cd='%s.outputX'%culSoftPct,
dv=0.0,v=0.0,itt='clamped',ott='clamped')
mc.setDrivenKeyframe(culSoftLength,at=('input1'),cd='%s.outputX'%culSoftPct,
dv=1.0,v=1.0,itt='clamped',ott='clamped')
angle = mc.keyTangent(cv,q=True,index=(0,0),inAngle=True)[0]
mc.keyframe(cv,index=(1,1),floatChange=2)
mc.keyTangent(cv,index=(1,1),itt='flat',ott='flat')
mc.keyTangent(cv,index=(0,0),inAngle=angle)
mc.setAttr('%s.preInfinity'%cv,1)
mc.setAttr('%s.postInfinity'%cv,1)
あと、preInfinity、postInfinityの設定も。
求めた長さの整理
↑ここなので、最終的に求めたい長さを取得するには、もう少し料理が必要です。
まず、
(補完がかかった部分の長さ)+(補完がかかるまでの長さ)=(補完後のIKの長さ)
つぎに、
(補完後のIKの長さ)/(補完前のIKの長さ)=(補完割合)
このようにして、softIKの補完割合を求めてしまえば、現時点のbaseTrnsから補完後のsoftTrnsの位置を計算できるようになります。
#cul softLength
culAllSoftLength = mc.shadingNode('addDoubleLinear',au=True,n='culAllSoftLength')
mc.connectAttr('%s.output'%culSoftLength,'%s.input1'%culAllSoftLength)
mc.connectAttr('%s.startLengthSoftCorrection'%nt,'%s.input2'%culAllSoftLength)
culSoftLengthPct = mc.shadingNode('multiplyDivide',au=True,n='culSoftLengthPct')
mc.setAttr('%s.operation'%culSoftLengthPct,2)
mc.connectAttr('%s.output'%culAllSoftLength,'%s.input1X'%culSoftLengthPct)
mc.connectAttr('%s.distance'%distNode,'%s.input2X'%culSoftLengthPct)
softedTrns = mc.shadingNode('multiplyDivide',au=True,n='softedTrns')
mc.connectAttr('%s.outputX'%culSoftLengthPct,'%s.input1X'%softedTrns)
mc.connectAttr('%s.outputX'%culSoftLengthPct,'%s.input1Y'%softedTrns)
mc.connectAttr('%s.outputX'%culSoftLengthPct,'%s.input1Z'%softedTrns)
mc.connectAttr('baseTrns.translateX','%s.input2X'%softedTrns)
mc.connectAttr('baseTrns.translateY','%s.input2Y'%softedTrns)
mc.connectAttr('baseTrns.translateZ','%s.input2Z'%softedTrns)
conditionNodeで挙動をスイッチ
softIK補完が開始されていないときは、
baseTrns==softTrns
という位置関係が保たれているので、currentLengthとstartLengthSoftCorrectionの値を使ってconditionNodeで計算を切り替えるようにします。
#createCondition
cond = mc.shadingNode('condition',au=True,n='checkCurrentDistance')
mc.setAttr('%s.operation'%cond,2)
mc.connectAttr('%s.distance'%distNode,'%s.firstTerm'%cond)
mc.connectAttr('%s.startLengthSoftCorrection'%nt,'%s.secondTerm'%cond)
mc.connectAttr('baseTrns.translateX','%s.colorIfFalseR'%cond)
mc.connectAttr('baseTrns.translateY','%s.colorIfFalseG'%cond)
mc.connectAttr('baseTrns.translateZ','%s.colorIfFalseB'%cond)
mc.connectAttr('%s.outputX'%softedTrns,'%s.colorIfTrueR'%cond)
mc.connectAttr('%s.outputY'%softedTrns,'%s.colorIfTrueG'%cond)
mc.connectAttr('%s.outputZ'%softedTrns,'%s.colorIfTrueB'%cond)
さいごに、conditionNodeとsoftTrnsノードを接続してやれば…
#connect softIK
mc.connectAttr('%s.outColorR'%cond,'softTrns.translateX')
mc.connectAttr('%s.outColorG'%cond,'softTrns.translateY')
mc.connectAttr('%s.outColorB'%cond,'softTrns.translateZ')
以上の実装に
- 補完の開始距離
- 補完の強さ
…などを調整可能になるよう、追加実装を施してあげれば、アニメーターさんが使いやすくなるのではないでしょうか。