|

Building Ragdolls ラグドールを作る

Let’s use the method called Verlet Integration to create simple physics simulations. We are going to implement complex features like collision detection. Just by combining simple dots and lines, we will make a skeleton for a dancing doll like this.

ベレ法という手法を使って簡単な物理シミュレーションを作ってみましょう。衝突判定など複雑な機能は実装しません。シンプルな点や線を部品として組み合わせて、こんな踊る人形の骨組みを作ってみます。

Existing libraries and tools would work better for proper simulations, but knowing a technique like this can be helpful understanding how these work, or when you want to have freedom in inventing interesting movements.

きちんとしたシミュレーションには既存のライブラリやツールを用いる方が良いですが、こうした方法を知っておくと仕組みを理解したり、自由に面白い動きを作りたい時などに助けになります。

Points

First we define points.

まず点を定義します。

In this implementation, instead of explicitly keeping velocity as a variable of the object, we substitute it with the change from the previous position to the current position. If no force is applied, the object is assumed to continue its uniform motion, and if the position changes, the difference is simply considered the velocity.

この実装では、物体に属する変数として速度を明示的に保持する代わりに、前の位置から現在の位置への変化量で代用します。力が加わらなければ物体は等速運動を続けるものとし、位置が変更された場合はその差分を単純に速度とみなすわけです。

class VerletPoint {
  constructor(p) {
    this.setPosition(p);
  }

  setPosition(p) {
    this.position = p;
    this.prevPosition = this.position.copy();
  }

  update() {
    let temp = this.position.copy();
    this.position.add(this.getVelocity());
    this.prevPosition = temp;
  }

  setVelocity(v) {
    this.prevPosition = this.position.copy().sub(v);
  }

  getVelocity() {
    return this.position.copy().sub(this.prevPosition);
  }

  draw() {
    ellipse(this.position.x, this.position.y, 8, 8);
  }
}

You can find these equations below on Wikipedia. The first equation approximates the equations of motion, which can be transformed into the second equation for finding the future position by using the difference between past and present positions.

Wikipediaには下記の式が載っています。上は運動方程式の近似で、変形すると過去の位置と現在の位置の差分を使って未来の位置を求める下の式になります。

an=Δ2xnΔt2=(xn+1xn)(xnxn1)Δt2=xn+12xn+xn1Δt2a_n = \frac{\Delta^2 x_n}{\Delta t^2} = \frac{(x_{n+1} - x_n) - (x_n - x_{n-1})}{\Delta t^2} = \frac{x_{n+1} - 2 x_n + x_{n-1}}{\Delta t^2}

xn+1=2xnxn1+anΔt2x_{n+1} = 2 x_n - x_{n-1} + a_n \Delta t^2

ana_n is the acceleration at time nn, xnx_n is the position, so (xn+1xn)(x_{n+1}-x_{n}) is the change in position from present to slightly future, and (xnxn1)(x_{n}-x_{n-1}) is the change in position from slightly past to present.

ana_nが時刻nnにおける加速度、xnx_nは位置、(xn+1xn)(x_{n+1}-x_{n})は現在から少し未来への位置の変化、(xnxn1)(x_{n}-x_{n-1})は少し過去から現在への位置の変化です。

Or, it might be simpler to think of (xnxn1)(x_{n}-x_{n-1}) approximates the current velocity, and the next position xn+1x_{n+1} as the sum of the current position xnx_n and this velocity. Please also refer to the page on numerical differentiation.

(xnxn1)(x_{n}-x_{n-1}) は現在の速度を近似したもの、この速度を現在の位置 xnx_n に足したものが次の時間の位置 xn+1x_{n+1} と考えた方がシンプルかもしれません。数値微分についてのページも参考にしてください。

getVelocity() {
  return this.position.copy().sub(this.prevPosition);
}

Acceleration ana_n is not directly represented in the code, but it is implicitly incorporated. If you change the position, the value returned by getVelocity will also change.

コードでは加速度 ana_n は直接表現されていませんが、position を変更すればgetVelocity の返す値も変わるという形で折り込まれています。

Sticks

Next, we make sticks. A stick connects two two points and keeps the distance between them constant. Since triangles made with three sticks are more stable and don’t collapse, we can make a sort of rigid body as well.

次に棒を作ります。棒は2つの点を繋いでその間の距離を一定に保つ働きをします。3つの点を繋いで3角形にすると形が安定して潰れないので剛体の様なものも作ることができます。

class VerletStick {
  constructor(a, b) {
    this.pa = a;
    this.pb = b;
    this.length = this.pa.position.dist(this.pb.position);
  }

  update() {
    let dist = this.pa.position.dist(this.pb.position);
    let diff = this.length - dist;
    let offset = p5.Vector.sub(this.pa.position, this.pb.position).mult(diff / dist / 2);
    this.pa.position.add(offset);
    this.pb.position.sub(offset);
  }

  draw() {
    let p0 = this.pa.position;
    let p1 = this.pb.position;
    line(p0.x, p0.y, p1.x, p1.y);
  }
}

Repeating short sticks can emulate a soft material like a string.

短い棒を繰り返し繋ぐことで、紐のような柔らかい素材を再現することもできます。

Ragdoll

ラグドール

A ragdoll is a simple puppet with joints connecting its limbs as rigid bodies, and is often used in computer games to replace a fixed animation when a character is falling down.

ラグドールとは手足を剛体として関節を繋いだだけの簡単な人形のようなもので、コンピュータゲームでキャラクターがやられた時に、固定のアニメーションの代わりとしてよく使われます。

Here, instead of having the character get hurt and fall, let’s make it dance with some energy. Ragdolls tend to look unnatural with realistic graphics because they do not consider the orientation of joints and muscles, but that is not a big deal with a point-and-line puppet (And puppets are often a little sad, awkward and beautiful that way).

ここでは傷つき倒れる代わりに元気よく踊ってもらいましょう。ラグドールは関節や筋肉の向きを考慮しないのでリアルなグラフィックだと不自然に見えがちですが点と線だけの人形劇なら問題になりません(操り人形は少し悲しくぎこちなく、それで美しいものです)。

This technique can be easily extended to 3D; just add z-coordinates and it works exactly the same way.

この手法は簡単に3Dに拡張できます。z座標を追加するだけで全く同じように動作します。

On citadel of Erbil, Kurdistan, 2022

See Codifying materials (1) for the texture applied to these dolls.