ひじの作成
前回作成した上腕に繋がる関節(ひじ)部分を作る。
ボーンを作成する
makePipeptと同様に、まずボーンの座標を計算する。
//make origin const radius = upperArmObj.thick[0]; const origin = new THREE.Vector2( 0,-radius ); origin.rotateAround( center2D, lastAngle );
radiusは円の半径(関節の太さ)。上腕の太さから取得。均一の太さにするので配列にしない。
次に、ボーンベクトルを回転させる際の中心点となるoriginを取得する。
originはupperArmを真横から見た時に、先端の一番下側の点になる(下の図参照)
上腕の最後の角度によってorigin座標が変わるので、グローバルにlastAngle変数を用意して、makePipePt関数内で最後の円の角度をlastAngle変数に格納するようにした。
originを中心としてboneベクトルを回転させることで各ボーンの座標を取得する。
const bone = new THREE.Vector2(); const pt = []; for( let i=0; i<( seg+1 ); i++ ){ pt[i] = []; let angle = i==0 ? 0 : bend / obj.seg; bone.rotateAround( origin, angle ); for( let j=0; j<edge; j++ ){ const theta = j * 2 * PI / edge; const w = radius * cos( theta ); const h = radius * sin( theta ); const v = new THREE.Vector2( 0, h ); v.add( bone ); v.rotateAround( bone, i * angle + lastAngle ); pt[i][j] = [ v.x, v.y, w ]; } }
angleにボーンのベクトルを回転させる角度をセットし、originを中心にしてセグメントごとに回転させる。
あとはmakePipePtと同じ要領で、各ボーンを中心にした円の座標を計算している。
関数化したものが以下。
function makeJointPt( obj, bend ){ //make origin const radius = obj.thick[0]; const origin = new THREE.Vector2( 0,-radius ); origin.rotateAround( center2D, lastAngle ); //set pt const bone = new THREE.Vector2(); const pt = []; for( let i=0; i<( obj.seg+1 ); i++ ){ pt[i] = []; let angle = i==0 ? 0 : bend / obj.seg; bone.rotateAround( origin, angle ); for( let j=0; j<obj.edge; j++ ){ const theta = j * 2 * PI / obj.edge; const w = radius * cos( theta ); const h = radius * sin( theta ); const v = new THREE.Vector2( 0, h ); v.add( bone ); v.rotateAround( bone, i * angle + lastAngle ); pt[i][j] = [ v.x, v.y, w ]; } } //update values lastAngle += bend; //lastAngleの値を更新する。 lastBonePos = bone; //boneの終端の値をlastBonePosに格納する。 return pt; }
メッシュの作成
function jointInit(){ const pt = makeJointPt( upperArmObj, -0.05 ); jointArmGeo = makeGeometry( upperArmObj, pt ); jointArmMesh = new THREE.Mesh( jointArmGeo, upperArmMat ); scene.add( jointArmMesh ); }
makeJointPtで座標を取得、この時角度が0だとfacelessなmeshになってしまいエラーが出るので、とりあえず小さい数字を渡している。
ジオメトリとメッシュを作成してシーンに追加。必要パラメータがupperArmと一緒なのでとりあえずupperArmObjを渡しているが、別途jointArmObjを用意してもよいのかも。
アニメーションする
function jointArmUpdate( angle ){ const bend = mapping( angle, 0.0, 1.5, -0.01, -3*PI/4 ); const pt = makeJointPt( upperArmObj, bend ); updateGeometry( upperArmObj, pt, jointArmGeo ); jointArmMesh.position.set( upperArmObj.ep.x, upperArmObj.ep.y, 0 ); }
0(曲げない)~1.5(最大限曲げる)の間で角度をマッピングし、makeJointPtで新しい座標を取得、updateGeometryでジオメトリを更新する。
upperArmの先端があるポイントにひじのメッシュの初期位置を移動させる。
function limbupdate( bend1, bend2 ){ lastAngle = 0; lastPos = new THREE.Vector2(); upperArmUpdate( bend1 ); jointArmUpdate( bend2 ); };
upperArmUpdate⇒jointUpdateの順番でジオメトリを更新する関数を作成。
ジオメトリをアップデートする関数の最初にlastAngleとlastPosを初期化する処理を入れておく。
(※これがないとアニメーションループごとに値が増えていってしまう)
キーフレームの作成
const upperArmMove = new THREE.Object3D(); const dur = [ 0, 2, 4 ]; const val1 = [ 2, -1, 2 ]; const val2 = [ 0, 1.5, 0 ]; const upperArmPos = []; for( let i=0; i<dur.length; i++ ){ upperArmPos.push( val1[i] ); upperArmPos.push( val2[i] ); upperArmPos.push( 0 ); } const uppperArmKF = new THREE.NumberKeyframeTrack( '.position', dur, upperArmPos );
今回はupperArmとJointの両方を同時に動かすため、ダミーで作成したObject3DのuserDataではなく、positionをパラメータとして使う。
positionを使うことでx, y, zの3つのパラメータが同時に設定できる。
position.xにupperArmの曲がり具合を、position.yにJointの曲がり具合を決める値を入れている。
function render(){ //animation update mixer.update(clock.getDelta()); let angle1 = upperArmMove.position.x; let angle2 = upperArmMove.position.y; limbupdate( angle1, angle2 ); //cycle requestAnimationFrame(render); renderer.render(scene, camera); }
レンダリングサイクル内で、positionからパラメータをそれぞれ取得し、limbUpdateでメッシュのジオメトリを更新する。
動いているところは以下のようになる。
(境目が気になるので、個別にメッシュを作成⇒くっつける、ではなく1個のメッシュにしようかなとおもう。。)