home processing download documents tutorial python tutorial gallery source about
 チュートリアル (トピック一覧へ戻る)

分岐パーティクル・エージェント・アルゴリズム

     ばね力とパーティクルによる線エージェント

このページではパーティクル・エージェントと分岐エージェントを組み合わせて 分岐増殖しつつも力の影響を外部からも内部からも受けるエージェントを作成します。

まず最初のコードでは分岐を行う線エージェントLineAgentクラス を作成しますが。このクラスは以前のチュートリアルの分岐エージェント に類似していますが、 IAgentクラスではなくIParticleクラスを継承して作成することにより 力の場や張力に反応できるようにします。 そしてアップデート・メソッドでは、線を生成するだけではなく、張力を持った線エージェントでもある ISpringLineを生成し、端点に自分のパーティクルと、子エージェントのパーティクルを繋ぎます。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  new LineAgent(new IParticle(0,0,0).hide().fric(0.2),
		new IVec(0,2,0));
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{
  LineAgent parent;
  IVec dir;
  
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }

  void update(){
    if(time()==0 && alive()){
      if(parent!=null && parent.alive()){
        //line between this and parent with spring force 100
        new ISpringLine(this,parent,100).clr(0); 
      }
      new LineAgent(this, dir);
    }
  }
}

下のコードでは複数のアトラクタ―により力の場を形成して、 線エージェントの反応する様子を見ます。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  new LineAgent(new IParticle(0,0,0).hide().fric(0.2),
		new IVec(0,2,0));
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,0,0,200,400,0)).linear(100).intensity(-200); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{
  LineAgent parent;
  IVec dir;
  
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }

  void update(){
    if(time()==0 && alive()){
      if(parent!=null && parent.alive()){
        new ISpringLine(this,parent,100).clr(0); //line with spring force
      }
      new LineAgent(this, dir);
    }
  }
}


     分岐動作の追加

上のコードではまだ線エージェントは分岐をしておらず、エージェントは一本の繋がった線しか 生成していませんでしたが、次にアップデート・メソッドに分岐規則を追加します。 以下のコードでは5%の確率で二つ目の枝となる子エージェントを生成します。 また、線の色は生成時刻に応じて変化します。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,0,0,200,400,0)).linear(100).intensity(-200); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void update(){
    if(time()==0 && alive()){
      if(parent!=null && parent.alive()){
        ISpringLine ln = new ISpringLine(this, parent,100); //line with spring force
        double t = -cos(IG.time()*0.06)*0.5+0.5; 
        ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2); //color by time
      }
      new LineAgent(this, dir);
      IVec dir2 = dir.cp();
      dir2.rot(PI*0.12);
      if( IRand.pct(5) ){ // 5% branching probability
        new LineAgent(this,dir2);
      }
    }
  }
}


     衝突判定

以下のコードでは線の衝突判定規則をインタラクト・メソッドに追加します。 衝突判定は 線分の交差を計算することによって判定され、結果はブーリアン変数isColliding に収められ、それがfalseであれば線と子エージェントを生成し、 そうでなければそのエージェントは del()メソッドによりそこで消去されます。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,0,0,200,400,0)).linear(100).intensity(-200); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding; 
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time 
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if(intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.06)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 1.0); // color by time
        }
        new LineAgent(this, dir);
        IVec dir2 = dir.cp();
        dir2.rot(PI*0.12);
        if( IRand.pct(5) ){ // 5% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}


     分岐と成長角度の制御

上記の例では、生成された線は平行線と一定の角度を持ったものでしたが、 次のコードでは、子エージェントの方向が左右にある角度をもって曲がっていくように 角度が調節されています。また、一つ目の子エージェントは必ず生成され、二つ目の 子エージェント20%の確率で生成されますが、一つ目のエージェントのが左右のどちらの方向へ 曲がるかは50%の確率で決められています。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-100); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time 
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.06)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 1.0); // color by time
        }
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}

     ばねの長さの設定

これまでのコードでは、ばね力を持った線エージェントISpringLineの長さは 指定されておらず、初期値の線の長さが発生するばね力がゼロになるばねの長さでした。 次の例では、明示的に長さがlen()メソッドを用いて指定されています。
ln.len(ln.len()*2);
指定は初期値のばねの長さの2倍を新しいばねの長さとしているので、 結果として、ばねの線は生成後に伸びようとします。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-100); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.06)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 1.0);
          ln.len(ln.len()*2); //set spring length longer
        }
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}

分岐パーティクル・エージェントが成長してゆくなかで、ばねがさらに伸びようとする結果、 線がジグザグに荒れる現象が見られます。


     板ばね力による線の円滑化

次に、エージェントの生成する線を滑らかにつなぐ方法を考えます。  現在のばね力は、2点間の距離を制御するだけなので3点間の角度にはなにも影響を及ぼしませんが、 3点を一直線に並べようとする板ばねのような力があれば、線が滑らかになりえます。 IStraightenerCurveクラスはそのような力を適応するエージェントです。 3つのパーティクル・エージェント(現エージェント、親エージェント、親の親エージェント)に対して この力を適応します。 また、このクラスはその3点間に曲線も生成します。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-30); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time 
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.015)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.5); // color by time
          if(parent.parent!=null && parent.parent.alive()){
            IStraightenerCurve st = 
		new IStraightenerCurve(this,parent,parent.parent).tension(100);
            st.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.5);
          }
        }
      }
      if(time()==4){
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}


     ばね力による近傍のエージェントの結束

次のコードでは、インタラクト・メソッドで衝突判定後に、距離の近い他のLineAgentクラスの エージェントに ばね力をもつISpringLineを生成し、近傍のエージェントを束ねようとします。 また、束ねるときにはばねの線に対してある程度の長さを与えて、束が固まりすぎないようにしています。 また、LineAgentには変数rootが追加され、そのエージェントがどこから伸びてきたものであるかを 判定し、同一の枝にあるエージェントは繋がないようにします。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2), null);
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-30); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent, root;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir, LineAgent root){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
    if(root!=null){ this.root = root; }
    else{ this.root = this; }
  }
  
  void interact(ArrayList< IDynamics > agents){ 
    if(time()==0){ //only in the first time
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
    else if(time()==1){
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this && lineAgent.alive()){ //agents include "this"
            if(lineAgent.root != root){ // different branch
              IVec dif = lineAgent.pos().dif(pos());
              if(dif.len() < dir.len()*2.5 && dif.len()>dir.len()){ // within threshold
                ISpringLine sp = 
			new ISpringLine(lineAgent,this,5,dir.len()*1.5).tension(5);
                double t = -cos(IG.time()*0.015)*0.5+0.5;   
                sp.hsb(0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.015)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2); // color by tim
          if(parent.parent!=null && parent.parent.alive()){
            IStraightenerCurve st
		= new IStraightenerCurve(this,parent,parent.parent).tension(100);
            st.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
          }
        }
      }
      if(time()==4){
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir, root);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2, null);
        }
      }
    }
  }
}


     力の場の適用

力の場を操作することによる全体の分岐の構成がどうなるかをみるために、 これまであるアトラクタ―に加えて、渦のような力を及ぼす IPointCurlFieldsetupメソッドに追加します。

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2), null);
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-30);//repulsion force
  }
  new IPointCurlField(new IVec(0,0,0), new IVec(0,0,1)).intensity(50); //spinning force
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent, root;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir, LineAgent root){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
    if(root!=null){ this.root = root; }
    else{ this.root = this; }
  }
  
  void interact(ArrayList< IDynamics > agents){ 
    if(time()==0){ //only in the first time
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
    else if(time()==1){
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this && lineAgent.alive()){ //agents include "this"
            if(lineAgent.root != root){ // different branch
              IVec dif = lineAgent.pos().dif(pos());
              if(dif.len() < dir.len()*2.5 && dif.len()>dir.len()){ // within threshold
                ISpringLine sp = 
			new ISpringLine(lineAgent,this,5,dir.len()*1.5).tension(5);
                double t = -cos(IG.time()*0.015)*0.5+0.5;   
                sp.hsb(0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.015)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2); // color by tim
          if(parent.parent!=null && parent.parent.alive()){
            IStraightenerCurve st
		= new IStraightenerCurve(this,parent,parent.parent).tension(100);
            st.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
          }
        }
      }
      if(time()==4){
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir, root);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2, null);
        }
      }
    }
  }
}


(トピック一覧へ戻る)

HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java / Python)
GALLERY
SOURCE CODE(GitHub)
PRIVACY POLICY
ABOUT/CONTACT