チュートリアル | (トピック一覧へ戻る) |
位置、速度、加速度、力
一方、力と質量と加速度は、ニュートンの運動方程式によって関係づけられています。
ニュートンの運動法則に従って パーティクルの動きをシミュレートするエージェント・クラスを実装するには、 まず位置、速度、加速度、力、質量の変数をフィールドとして追加します。 以下のコードでは質量はdouble型、他のフィールドはIVec型 としています。 通常、パーティクルのシミュレーションは 初期位置と初速度を与えて実行するので、 コンストラクタの引数に位置と速度の2つの変数をとります。
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
class MyParticle extends IAgent{
IVec pos;
IVec vel;
IVec acc;
IVec frc;
double mass = 1.0; // default mass
MyParticle(IVec p, IVec v){
pos = p;
vel = v;
}
}
位置と速度はパーティクルの内部状態と言えますが、 力は外部から与えられるものです。 シミュレーションのタイム・フレームごとに、 パーティクルは力を受け、それに応じて速度と位置が更新されます。 一つのパーティクルが同時に複数の力受けるとき、 それは、それらのベクトル和の力を一回受けることと等しくみなせます。
以下の図にベクトル和の例を示します。
パーティクル・エージェントが力を外部または内部から受けるための push()メソッドを以下のように実装します。
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
class MyParticle extends IAgent{
IVec pos, vel, acc, frc;
double mass = 1.0; // default mass
MyParticle(IVec p, IVec v){
pos = p;
vel = v;
frc = new IVec();
}
void push(IVec f){
frc.add(f);
}
}
複数の力の各々の力ベクトルでなく、 合算された力のベクトル和が一つあれば 加速度、速度、位置は計算できます。 加速度、速度、位置は数学的には以下のように計算されます。
パーティクル・クラスの実装![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
class MyParticle extends IAgent{
final double dt = IConfig.updateRate; //second per frame
IVec pos, vel, acc, frc;
double mass = 1.0; // default mass
MyParticle(IVec p, IVec v){
pos = p;
vel = v;
frc = new IVec();
}
void push(IVec f){
frc.add(f);
}
void update(){
acc = frc.div(mass);
vel.add(acc.mul(dt));
pos.add(vel.cp().mul(dt)); //vel itself can't be changed
frc.zero(); //reset frc by setting zero
}
}
変数dtはエージェントが更新されるタイム・フレーム間の秒数です。(通常は0.03-0.1秒程度) この変数は定数なのでfinalというキーワードが付加されます。 iGeoのデフォルトの更新秒数はIConfig.updateRateで設定されています。 iGeoシステムの更新秒数はIG.updateRate(double)メソッドや その別名短縮メソッドIG.rate(double)やIG.speed(double)で設定することができます。 アップデート・メソッドでは、加速度accが力frcをmassで割ることによって計算されています。 それから速度velは、微小更新時間分の加速度の累積を加算することで更新されます。 具体的にはaccにdtを掛けたものを足します。 同様に位置を更新するには、速度velに微小時間dtかけたものを足しています。 ただし、速度は元の値を保持する必要があるため、cp()メソッドで複製されてから掛け算されています。 そしてアップデート・メソッドの最後では力frcをゼロにします。
パーティクル・エージェントを用いるとき、だいたいは何らかの力を パーティクルに作用し、なおかつ、その中でなんらかの幾何学オブジェクトを生成します。 以下の例では、パーティクル・エージェント自身が自分にY軸の下の方向に力を与え、 またそれぞれの時点での位置に点を生成して行きます。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
new MyParticle(IG.v(0,0,0), IG.v(10,20,0));
}
class MyParticle extends IAgent{
final double dt = IConfig.updateRate;//second per frame
IVec pos, vel, acc, frc;
double mass = 1.0; // default mass
MyParticle(IVec p, IVec v){
pos = p;
vel = v;
frc = new IVec();
}
void push(IVec f){
frc.add(f);
}
void update(){
push(IG.v(0,-10,0));
new IPoint(pos.cp());
acc = frc.div(mass);
vel.add(acc.mul(dt));
pos.add(vel.cp().mul(dt)); //vel itself can't be changed
frc.zero(); //reset frc by setting zero
}
}
以下の例ではコードの構造化のために、 パーティクルの動きをシミュレートする部分と、幾何学オブジェクトを生成する部分を分離し、 前者をMyParticleBaseに入れ、 後者をMyParticleBaseを継承するMyParticleに入れました。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
new MyParticle(IG.v(0,0,0), IG.v(10,20,0));
}
class MyParticle extends MyParticleBase{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){
push(IG.v(0,-10,0));
new IPoint(pos.cp());
super.update();
}
}
class MyParticleBase extends IAgent{
final double dt = IConfig.updateRate;//second per frame
IVec pos, vel, acc, frc;
double mass = 1.0; // default mass
MyParticleBase(IVec p, IVec v){
pos = p;
vel = v;
frc = new IVec();
}
void push(IVec f){ frc.add(f); }
void update(){
acc = frc.div(mass);
vel.add(acc.mul(dt));
pos.add(vel.cp().mul(dt)); //vel itself can't be changed
frc.zero(); //reset frc by setting zero
}
}
IParticleクラスの利用![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
new MyParticle(IG.v(0,0,0), IG.v(10,20,0));
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){
push(IG.v(0,-10,0));
new IPoint(pos().cp());
}
}
また、IParticleクラスを継承して独自の子クラスを作成するときには、 アップデート・メソッドに super.update(); と記述する必要はありません。
以下はIParticleを継承して独自の振る舞いをするパーティクル・エージェントの もう一つの例です。 アップデート・メソッドでランダムな力を自身に与えています。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
new MyParticle(IG.v(0,0,0), IG.v(0,0,0));
new MyParticle(IG.v(0,0,0), IG.v(0,0,0));
new MyParticle(IG.v(0,0,0), IG.v(0,0,0));
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){
IVec force = IRand.pt(-100,-100,0,100,100,0);
push(force);
new IPoint(pos().cp());
}
}
もう一つの例は、パーティクルの速度に応じて計算した力ベクトルを 自身に適用しています。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
new MyParticle(IG.v(0,0,0), IG.v(10,0,0));
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){
IVec force = vel().cp().mul(2).rot(PI/2);
push(force);
new IPoint(pos().cp());
}
}
パーティクル間のインタラクション![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
for(int i=0; i < 10; i++){
new MyParticle(IG.v(0,i*10,0), IG.v(10,0,0)).clr(0.1*i,0,0);
}
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void interact(ArrayList < IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyParticle){
MyParticle p = (MyParticle)agents.get(i);
if(p != this){
if(p.dist(this) < 20){ //distance threshold
IVec force = p.pos().dif(pos());
force.mul(0.01);
push(force);
}
}
}
}
}
void update(){
new IPoint(pos().cp()).clr(clr());
}
}
インタラクト・メソッドでパーティクルは、他のパーティクルの位置をチェックし、 もしそれがある距離より近ければ、相手のパーティクルからの差ベクトルを計算し、 それにある値をかけ、自身に力ベクトルとして適用しています。 その結果、パーティクルはお互いが引き合っているような振る舞いをみせます。
以下の例は、 上記にある例の、速度に応じた力で回転するパーティクル・エージェントに、 ひとつ前の例と同様の、引き合う動作をインタラクト・メソッドに定義し、 振る舞いの変化を見るためのものです。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
for(int i=0; i < 10; i++){
new MyParticle(IRand.pt(0,0,100,100), IG.v(10,0,0)).clr(IRand.clr());
}
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void interact(ArrayList < IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyParticle){
MyParticle p = (MyParticle)agents.get(i);
if(p != this){
if(p.dist(this) < 20){
IVec force = p.pos().dif(pos());
force.mul(1.2);
push(force);
}
}
}
}
}
void update(){
IVec force = vel().cp().mul(2).rot(PI/2);
push(force);
new IPoint(pos().cp()).clr(clr());
}
}
次のコードは ランダム・ベクトルをランダム・ウォークの動作と 引き合うインタラクト・メソッドを組み泡あせたものです。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
for(int i=0; i < 30; i++){
new MyParticle(IRand.pt(0,0,100,100), IG.v(10,0,0)).clr(IRand.clr());
}
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void interact(ArrayList < IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyParticle){
MyParticle p = (MyParticle)agents.get(i);
if(p != this){
if(p.dist(this) < 20){
IVec force = p.pos().dif(pos());
force.mul(2.0);
push(force);
}
}
}
}
}
void update(){
IVec force = IRand.pt(-100,-100,0,100,100,0);
push(force);
new IPoint(pos().cp()).clr(clr());
}
}
パーティクルに力を及ぼす力の場エージェント![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
new MyParticle(IG.v(0,0,0), IG.v(10,20,0));
new MyGravity();
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){
new IPoint(pos().cp());
}
}
class MyGravity extends IAgent{
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
particle.push(IG.v(0,-10,0));
}
}
}
上記の例では、短縮版のインタラクトメソッド
void interact(IDynamics agent)
を用いています。
インタラクト・メソッドの中のこのif条件分岐
では、該当エージェントがMyParticleのインスタンスであるかどうかをチェックしています。
もしそうであれば、IDynamics型の変数agentは
MyParticle型の変数particleにキャストにより変換されたのち、
push()メソッドによって力が適用されます。
interact()メソッドの詳細については
該当チュートリアル参照してください。
次のコードは上のコードを少し変更して、 MyGravityクラスにIVec型のフィールドgravityを加えて、 コンストラクタで重力の方向と大きさを設定可能にしてあります。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
new MyParticle(IG.v(0,0,0), IG.v(10,20,0));
new MyGravity(IG.v(0,-10,0));
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){ new IPoint(pos().cp()); }
}
class MyGravity extends IAgent{
IVec gravity;
MyGravity(IVec g){ gravity=g; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
particle.push(gravity);
}
}
}
以下のコードでは、初期位置と初速度の異なる パーティクルに重力エージェントを適応しています。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
int num = 40;
for(int i=0; i < num; i++){
double a = i*2*PI/num;
new MyParticle(IG.v(20,0,0).rot(a),IG.v(10,0,i).rot(a));
}
new MyGravity(IG.v(0,0,-10));
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){ new IPoint(pos().cp()); }
}
class MyGravity extends IAgent{
IVec gravity;
MyGravity(IVec g){ gravity=g; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
particle.push(gravity);
}
}
}
パーティクルを跳ね返す平面エージェント![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
int num = 40;
for(int i=0; i < num; i++){
double a = i*2*PI/num;
new MyParticle(IG.v(20,0,0).rot(a), IG.v(10,0,i).rot(a));
}
new MyGravity(IG.v(0,0,-10));
new BouncePlane(-5);
}
class MyParticle extends IParticle{
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){ new IPoint(pos().cp()); }
}
class MyGravity extends IAgent{
IVec gravity;
MyGravity(IVec g){ gravity=g; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
particle.push(gravity);
}
}
}
class BouncePlane extends IAgent{
double z;
BouncePlane(double zval){ z=zval; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
if(particle.pos().z() < z){
particle.pos().add(0,0,z - particle.pos().z());
particle.vel().ref(IG.zaxis);
}
}
}
}
パーティクルの軌跡としての幾何学オブジェクト生成![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
int num = 40;
for(int i=0; i < num; i++){
double a = i*2*PI/num;
new MyParticle(IG.v(20,0,0).rot(a), IG.v(10,0,i).rot(a));
}
new MyGravity(IG.v(0,0,-10));
new BouncePlane(-5);
}
class MyParticle extends IParticle{
IVec prevPos;
MyParticle(IVec p, IVec v){ super(p,v); }
void update(){
IVec curPos = pos().cp();
if(prevPos != null){
new ICurve(prevPos, curPos).clr(0);
}
prevPos = curPos;
}
}
class MyGravity extends IAgent{
IVec gravity;
MyGravity(IVec g){ gravity=g; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
particle.push(gravity);
}
}
}
class BouncePlane extends IAgent{
double z;
BouncePlane(double zval){ z=zval; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
if(particle.pos().z() < z){
particle.pos().add(0,0,z - particle.pos().z());
particle.vel().ref(IG.zaxis);
}
}
}
}
上の例で先端に残っている点は、hide()メソッドで隠すことができます。
軌跡上に幾何学オブジェクトを生成するもう一つの方法は、 幾何学オブジェクトを生成ためだけの別のエージェント・クラスを作成することです。 以下の例では GeometrAgentクラスが追加され、 その内部に2つのパーティクルとそれぞれの以前の位置をフィールドとして持ちます。 コンストラクタでパーティクルのペアが与えられ、 アップデート・メソッドにおいて2つのパーティクルとそれぞれの一つ以前の位置の合計 4点から矩形の面を生成します。
![]()
![]()
![]()
![]()
import processing.opengl.*;
import igeo.*;
void setup(){
size(480, 360, IG.GL);
IG.duration(200);
int num = 40;
MyParticle[] particle = new MyParticle[num];
for(int i=0; i < num; i++){
double a = i*2*PI/num;
particle[i] =
new MyParticle(IG.v(20,0,0).rot(a), IG.v(10,0,i).rot(a));
}
for(int i=1; i < num; i++){
new GeometryAgent(particle[i-1], particle[i]);
}
new MyGravity(IG.v(0,0,-10));
new BouncePlane(-5);
}
class MyParticle extends IParticle{
IVec prevPos;
MyParticle(IVec p, IVec v){
super(p,v);
hide(); // hiding the point of IParticle
}
void update(){
IVec curPos = pos().cp();
if(prevPos != null){
new ICurve(prevPos, curPos);
}
prevPos = curPos;
}
}
class MyGravity extends IAgent{
IVec gravity;
MyGravity(IVec g){ gravity=g; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
particle.push(gravity);
}
}
}
class BouncePlane extends IAgent{
double z;
BouncePlane(double zval){ z=zval; }
void interact(IDynamics agent){
if(agent instanceof MyParticle){
MyParticle particle = (MyParticle)agent;
if(particle.pos().z() < z){
particle.pos().add(0,0,z - particle.pos().z());
particle.vel().ref(IG.zaxis);
}
}
}
}
class GeometryAgent extends IAgent{
MyParticle particle1, particle2;
IVec prevPos1, prevPos2;
GeometryAgent(MyParticle p1, MyParticle p2){
particle1 = p1;
particle2 = p2;
}
void update(){
IVec curPos1 = particle1.pos().cp();
IVec curPos2 = particle2.pos().cp();
if(prevPos1 != null && prevPos2 != null){
new ISurface(prevPos1, curPos1, curPos2, prevPos2).clr(clr());
}
prevPos1 = curPos1;
prevPos2 = curPos2;
// update color
double r = red() + IRand.get(-0.01,0.01);
double g = green() + IRand.get(-0.02,0.02);
clr(r, g, 1.0);
}
}
HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java /
Python)
GALLERY
SOURCE CODE(GitHub)
PRIVACY POLICY
ABOUT/CONTACT