全自動胸揺れへの道(3):LScriptでバネ(後編)

続き。絵がないとさみしいのでもう一丁作ってみた。
画像

このポーズは、胸ゆれテストのモーションの一部をSS用にいじって使ってます。


●棒バネの組み込み

ゴムは伸びた方向に復元力が発生するのに対し、棒バネは曲げに対して元の位置に戻るような復元力が発生します。
本来なら棒の素材とか棒の太さとかいろんな物理パラメータが存在するけど、例によってそれなりに動けばいいので、ごく単純に「たわみ」(重りが揺れた距離)に比例して元の方向に復元力が発生する、というモデルで実装します。

増やすパラメータとしては、

  • 曲がり対する復元係数
  • 元の軸からの最大の曲がり
の2つ。
最大の曲がりは、おっぱいが背中に回らないようにするためのもので、最大の伸びと同じ意味。
これを適切に設定すれば、今回のゆれものにはコリジョンがいらないって寸法。

スクリプトはこれ。
//振り子テスト(ばね、棒バネつき) by blueabyss
@version 2.3
myObj;

//定数 ---------------------

PI = 3.1415926;
G = 9.8;    //重力加速度

//ワーク -------------------

//最後に計算した速度(ワールド)
s_lastVel = <0, 0, 0>;

//最後に計算した重りの位置(向きはワールド、位置はボーンからの相対)
s_lastPos = <0, 0, 0>;

//最後のボーン位置(ワールド)
s_lastPosW = <0, 0, 0>;

//最後のボーン回転(ワールド)
s_lastRotW = <0, 0, 0>;

s_lastTime = 0;

//パラメータ ---------------

//ボーン原点から重りまでの長さ(0.001以上)
s_length = 1;

//重りの重さ(0.001以上)
s_weight = 1;

//伸び方向のばね係数(0以上、0の時はばねなし)
s_stretchConst = 100;

//ばねの最大の伸び(1.0以上)
s_maxStretch = 2.0;

//ばねの最大の縮み(0~1.0)
s_maxShrink = 0.5;

//定数ダンパー(0~1)
s_damper = 1;

//ローカルZ軸からの最大の傾き(0~180度)
s_maxRotAngle = 80;

//傾きに対する復元係数(0以上)
s_angleConst = 10;


create: obj {
  myObj= obj;
  setdesc("BA pendulum test4");
  (省略. pendulum2.ls 参照)
}

//速度、位置をリセット
resetParam {

  //フレーム開始時間を取得
  scene = Scene();
  s_lastTime = scene.framestart;

  //初速
  s_lastVel = <0, 0, 0>;

  //質点の初期位置 = ボーンの先っちょ
  pos = < 0, 0, s_length >;

  //ワールド位置に変換する
  parent = myObj.parent;
  if( parent != nil )
    s_lastRotW = parent.getWorldRotation( s_lastTime );
  else
    s_lastRotW = <0,0,0>;
  m = rotMatrix( s_lastRotW );
  rot = myObj.getRotation( s_lastTime );
  rm = rotMatrix( rot );
  m = mulMatrix( rm, m );
  pos = transform( m, pos );

  s_lastPosW = myObj.getWorldPosition( s_lastTime );
  s_lastPos = pos + s_lastPosW;

  //外部で参照できる変数に保存
  globalstore( "g_lastPos", s_lastPos );
}

process: ma, frame, time {

  //現在の時間が先頭フレームなら速度、位置をリセット
  scene = Scene();
  stime = scene.framestart;
  if( frame == stime ) {
    resetParam();
    applyCurrentPosition( ma, time );
    s_lastTime = time;
    return;
  }

  //デルタタイムを算出
  dt = time - s_lastTime;
  if( dt<=0 ) {
    //最後に計算した値を反映
    applyCurrentPosition( ma, time );
    return;
  }
  s_lastTime = time;

  //加速度
  va = <0, -G, 0>;

  //ばね ... 元の長さからの変位量に比例する復元力を発生
  e = s_lastPos - s_lastPosW;
  len = getLength( e );
  e = normalize( e );
  d = len - s_length;
  if( d != 0 ) {
    d *= s_stretchConst / s_weight;
    va -= e * d;
  }

  //棒ばね ... ローカルZ軸からの傾きに応じた復元力を発生
  m = rotMatrix( s_lastRotW );
  rot = ma.get(ROTATION,stime);
  rm = rotMatrix( rot );
  m = mulMatrix( rm, m );
  vf = ;
  vf = normalize( vf );
  ra = dot3d(e, vf);
  ra = acos( ra ) * 180 / PI;
  if( ra>0 && s_maxRotAngle>0 && s_maxRotAngle<180 ) {

    //傾きに対する加速度
//    d = rad( ra ) * len;
    d = rad( ra ) * s_length;  //こっちゃのほーがええ
    d *= s_angleConst / s_weight;

    //加速度の向き
    v = cross3d(e,vf);    //Z軸と傾きを含む平面の法線
    v = normalize(v);
    v = cross3d(v,e);
    v = normalize( v );
    va += v * d;
  }

  //新しいボーン位置 O'
  newO = myObj.getWorldPosition( time );

  //新しい重り位置 S'
  newS = s_lastPos + s_lastVel * dt;
  newS += va * (dt * dt * 0.5);  //加速度による位置更新

  //速度更新
  s_lastVel += va * dt;
  s_lastVel *= s_damper;  //ダンパー

  //一定量以上伸び縮みしないようにする
  e = newS - newO;
  len = getLength( e );
  e = normalize( e );
  d = len / s_length;
  if( d > s_maxStretch ) {
    if( s_stretchConst==0 ) {
      //差分距離を速度に換算 v = ds/dt
      d = (len - s_length * s_maxStretch) / dt;
      d *= 2;  //気持ち?
      s_lastVel -= e * d;
    }
    len = s_length * s_maxStretch;
    newS = newO + (e * len);
  } else if( d < s_maxShrink ) {
    len = s_length * s_maxShrink;
    newS = newO + (e * len);
  }

  //一定量以上曲がらないようにする
  parent = myObj.parent;
  if( parent != nil )
    s_lastRotW = parent.getWorldRotation( time );
  m = rotMatrix( s_lastRotW );
  rot = ma.get(ROTATION,stime);
  rm = rotMatrix( rot );
  m = mulMatrix( rm, m );
  vf = ;
  vf = normalize( vf );
  ra = dot3d(e, vf);
  ra = acos( ra ) * 180 / PI;
  if( ra > s_maxRotAngle ) {
    v = cross3d(e, vf);    //Z軸と傾きを含む平面の法線
    v = normalize(v);
    d = rad( s_maxRotAngle );
    m = rotAxisMatrix(v, d);
    e = transform( m, vf );
    newS = newO + (e * len);
  }

  s_lastPos = newS;
  s_lastPosW = newO;

  globalstore( "g_lastPos", newS );

  applyCurrentPosition( ma, time );
}

//計算済みの位置を反映
applyCurrentPosition : ma, time {
  (省略. pendulum2.ls 参照)
}

//3×3の回転マトリクスを作成 オーダーはZXY
rotMatrix : rot {
  (省略)
}

//行列の乗算
mulMatrix : m1, m2 {
  m;

  m[1] = m1[1] * m2[1] + m1[2] * m2[4] + m1[3] * m2[7];
  m[2] = m1[1] * m2[2] + m1[2] * m2[5] + m1[3] * m2[8];
  m[3] = m1[1] * m2[3] + m1[2] * m2[6] + m1[3] * m2[9];

  m[4] = m1[4] * m2[1] + m1[5] * m2[4] + m1[6] * m2[7];
  m[5] = m1[4] * m2[2] + m1[5] * m2[5] + m1[6] * m2[8];
  m[6] = m1[4] * m2[3] + m1[5] * m2[6] + m1[6] * m2[9];

  m[7] = m1[7] * m2[1] + m1[8] * m2[4] + m1[9] * m2[7];
  m[8] = m1[7] * m2[2] + m1[8] * m2[5] + m1[9] * m2[8];
  m[9] = m1[7] * m2[3] + m1[8] * m2[6] + m1[9] * m2[9];

  return m;
}

//任意軸 v に対する r 回転
//vは単位ベクトル、rはラジアン
rotAxisMatrix : v, r {
  m;

  cr = cos( r );
  sr = sin( r );
  cr2 = 1-cr;

  m[1] = v.x*v.x*cr2 + cr;
  m[2] = v.x*v.y*cr2 - v.z*sr;
  m[3] = v.z*v.x*cr2 + v.y*sr;
  m[4] = v.x*v.y*cr2 + v.z*sr;
  m[5] = v.y*v.y*cr2 + cr;
  m[6] = v.y*v.z*cr2 - v.x*sr;
  m[7] = v.z*v.x*cr2 - v.y*sr;
  m[8] = v.y*v.z*cr2 + v.x*sr;
  m[9] = v.z*v.z*cr2 + cr;

  return m;
}

//逆行列計算
invMatrix : mi {
  (省略)
}

//ベクトルを行列で変換
transform : mat, vec {
  (省略)
}

//ベクトルの長さを返す
getLength : v {
  (省略)
}

// Cのatan2()同等の処理
atan2 : y, x {
  (省略)
}


//設定
options {
  reqbegin("BA Pendulum test4");

  c_length = ctlnumber("Length", s_length);
  c_weight = ctlnumber("Weight", s_weight);
  c_stretchConst = ctlnumber("Stretch Constant", s_stretchConst);
  c_maxStretch = ctlnumber("Max Stretch Scale", s_maxStretch);
  c_maxShrink = ctlnumber("Max Shirink Scale", s_maxShrink);
  c_maxRotAngle = ctlnumber("Max Rotate Angle", s_maxRotAngle);
  c_angleConst = ctlnumber("Angle Constant", s_angleConst);
  c_damper = ctlnumber("Damper", s_damper);

  return if ! reqpost();

  s_length = getvalue( c_length );
  if( s_length<0.001 ) s_length = 0.001;

  s_weight = getvalue( c_weight );
  if( s_weight<0.001 ) s_weight=0.001;
  if( s_weight>9999 ) s_weight=9999;

  s_stretchConst = getvalue( c_stretchConst );
  if( s_stretchConst<0 ) s_stretchConst=0;
  if( s_stretchConst>999 ) s_stretchConst=999;

  s_maxStretch  = getvalue( c_maxStretch );
  if( s_maxStretch<1 ) s_maxStretch=1;
  if( s_maxStretch>999 ) s_maxStretch=999;

  s_maxShrink    = getvalue( c_maxShrink );
  if( s_maxShrink<0 ) s_maxShrink=0;
  if( s_maxShrink>1 ) s_maxShrink=1;

  s_maxRotAngle = getvalue( c_maxRotAngle );
  if( s_maxRotAngle<0 ) s_maxRotAngle=0;
  if( s_maxRotAngle>180 ) s_maxRotAngle=180;

  s_angleConst = getvalue( c_angleConst );
  if( s_angleConst<0 ) s_angleConst=0;

  s_damper = getvalue( c_damper );
  if( s_damper<0 ) s_damper=0;
  if( s_damper>1 ) s_damper=1;
}
だいぶ長くなってきて見るのもいやになってきたね。
これを適用したのがこの動画。






設定はこんな感じ。
画像

ジャンプして縦揺れを想定してみた。
設定はこんな感じね(ちょっと揺れを抑えた)。
画像







どうよ。
だいぶそれっぽくなってきたじゃない。


次回でゆれものはたぶん最終回。

ブログ気持玉

クリックして気持ちを伝えよう!

ログインしてクリックすれば、自分のブログへのリンクが付きます。

→ログインへ

なるほど(納得、参考になった、ヘー)
驚いた
面白い
ナイス
ガッツ(がんばれ!)
かわいい

気持玉数 : 0

この記事へのコメント