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

前回振り子運動をやったけど、あれではおっぱいの動きではないよね。
あと入れなければならない処理として、

  • 伸び方向と縮み方向にバネのような復元力を発生
  • 初期のボーン角度から曲がる量が大きい場合に復元力を発生
とゆー2つの処理を入れたい所。
後者は床から生えてるパンチングボールみたいな棒状のバネ風味。

伸び方向、縮み方向は、その変位量を使ってスケールに割り振ることで、さらにいい感じに演出できるとにらんでます。


さて今回のアプリショットはこんな感じ。
画像

ディティールの修正はスクリプティングに持ってかれてまったくできず。
ボディペイントのなんちゃってビキニってのもそろそろアレなんで、軽くバニーガール風味にしてみました。
(時間がないんでパーツは適当。)
バニーガール服はやっぱしボディペイントなんだけど、パンツの一部は別にポリゴン使ってるんで、ポリゴンをちょっと足してボディと繋いでます。


●振り子の改良

前回の振り子だとバネなどの処理を入れるにはやりづらいので、アルゴリズムを変えました。

  • 重りの位置は、前回は位置だけボーンからの相対にしていたけど、完全にワールド位置にする
  • ボーン自体の移動(O→O')と重りの移動(S→S')を同時に行う
    重りの移動 ⊿S = lastVel * dt + 0.5 * G * dt * dt
  • L=|O'S'| が糸の長さ len を越えない場合は終了。
    越える場合は (L - len) を法線方向( S'からO' )へ向かう速度に換算し、速度を更新。
      換算速度 vN = (L-len) / dt
    S'の位置を |O'S"|=len となる位置S"に修正

3の長さを調整する所で、伸びた分をバネの復元力にするって寸法。
縮み方向にも同じ処理を入れます。

改良したスクリプトがこれ。
//振り子テスト(改良版) 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_lastTime = 0;

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

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


create: obj {
  myObj= obj;
  setdesc("BA pendulum test2");

  //myObjがBONE Object Agentsであることを確認
  if( myObj.genus != BONE )
    error( "This script is only for bone." );

  //初期の長さ = ボーンのrestLength
  s_length = myObj.restlength;

  resetParam();
}

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

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

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

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

  //ワールド位置に変換する
  rot = myObj.getWorldRotation( s_lastTime );
  m = rotMatrix( rot );
  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();
  if( frame == scene.framestart ) {
    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>;

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

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

  //速度更新
  s_lastVel += va * dt;

  //O' から S' に向かうベクトル
  e = newS - newO;

  len = getLength( e );
  e = normalize( e );
  d = len - s_length;

  if( d > 0 ) {
    //O'S' が糸よりも長くなった

    //差分距離を速度に換算 v = ds/dt
    d /= dt;
    d *= 2;  //気持ち?
    s_lastVel -= e * d;

    //糸の長さをクリッピング
    newS = newO + e * s_length;
  }

  s_lastPos = newS;
  s_lastPosW = newO;

  globalstore( "g_lastPos", newS );

  applyCurrentPosition( ma, time );
}

//計算済みの位置を反映
applyCurrentPosition : ma, time {

  //相対位置
  dpos = s_lastPos - s_lastPosW;

  //質点Sをローカル座標系で変換
  parent = myObj.parent;
  if( parent != nil ) {
    rot = parent.getWorldRotation( time );
    m = rotMatrix( rot );
    m = invMatrix( m );
    dpos = transform( m, dpos );
  }

  //ボーンをS方向に向かせるためにベクトルからオイラー角を求める
  dpos = normalize( dpos );
  x = dpos.x;
  y = dpos.y;
  z = dpos.z;
  ex = atan2( x, z );
  ey = -atan2( y, sqrt( z * z + x * x ) );

  dpos = ma.get(ROTATION, time);  //Zはオリジナルを保持
  e = < deg(ex), deg(ey), dpos.z >;

  //向きをセット
  ma.set(ROTATION, e);
}

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

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

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

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

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

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

  c_length = ctlnumber("Length", s_length);

  return if ! reqpost();

  s_length = getvalue( c_length );
  if( s_length<0.001 ) s_length = 0.001;
}
パラメータが必要になってきたのでリクエスタを使ってます。
組み込んだ動画はこれ。






前回とほとんど変わらない動きになってますな。よっしゃよっしゃ。




●ばねの組み込み

ばねの運動は糸(…今回からはゴム)が伸びたり縮んだりした時に、元の長さに戻るよう、元の長さとなる方向に相応の復元力が発生することで実現します。
運動方程式などは例によってその筋を参照してください。
確かこんな感じだ。
  F = ma = -kx

とっととスクリプトを示します。
//振り子テスト(ばねつき) 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_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;


create: obj {
  myObj= obj;
  setdesc("BA pendulum test3");

  (省略. pendulum2.ls 参照)
}

//速度、位置をリセット
resetParam {
  (省略. pendulum2.ls 参照)
}

process: ma, frame, time {

  //現在の時間が先頭フレームなら速度、位置をリセット
  scene = Scene();
  if( frame == scene.framestart ) {
    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;
  }

  //新しいボーン位置 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);
  }

  s_lastPos = newS;
  s_lastPosW = newO;

  globalstore( "g_lastPos", newS );

  applyCurrentPosition( ma, time );
}

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

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

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

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

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

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


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

  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_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_damper = getvalue( c_damper );
  if( s_damper<0 ) s_damper=0;
  if( s_damper>1 ) s_damper=1;
}
リクエスタでのオプション項目が増えてます。

  • ゴムの長さ(デフォルトはボーンのrestlengthで、変更可能)
  • 重りの重さ
  • 復元力の強さ
  • 元から最大どれだけ伸びて(または縮んで)良いか(倍率)
  • 減衰率
といった辺り。

伸びや縮みを制限する倍率は、たとえば応用ちゃんがマッハのスピードで走った場合におっぱいだけが元の位置に取り残され、応用ちゃんが止まった瞬間におっぱいだけがマッハのスピードでバネ運動を始める予感がするので、それを抑制するためのもの。
復元力が弱い場合にはおっぱいが床にストンと落ちてしまう可能性もあるしね。
この制限値を越えた場合は長さだけクランプして、速度や加速度には換算しません。
なお今回のスクリプトでは、復元力を0にすると pendulum2.ls と同じように普通の振り子のよーに振舞います。

あと減衰率は、フレーム間隔に関わらず速度を指定した係数を乗じて減速させるもの。
これがないと、おっぱいは永遠に振動を続けてしまう。
(まあ誤差があるからそのうち止まるけど。)

これを組み込んだ動画がこれ。






設定はこんな感じ。
画像
ちなみにbreastの restlength は0.5mなので、人間にくらべるとこのボーンはだいぶでかいです。

LightWaveを持っている兄弟ならスクリプトを組み込んで遊んでみてください。
ソースが完全じゃないから繋げるのがちょっと面倒かも知れないけど。
頑張れば最後にはあなたも全自動胸揺れをゲットできます。

続いて…おっと、文字数制限に当たったので後半に続きます。

ブログ気持玉

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

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

→ログインへ

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

気持玉数 : 0

この記事へのコメント