ひじの作成
前回作成した上腕に繋がる関節(ひじ)部分を作る。

ボーンを作成する
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個のメッシュにしようかなとおもう。。)
