ODE Overview - eiichiromomma/CVMLAB GitHub Wiki

(ODE) Overview

ODEの概要についてのメモ

ODEの概念

物体の定義

  • Body:動力学の計算に用いるモノ
  • Geometry:衝突検出の計算に用いるモノ

※ODEでは画像としての可視化は出来ない

物体の可視化

ODEからは位置、形状、色、大きさなどの情報が得られるため、3D描画ツール(シーングラフ等)を用いればリアルタイムで可視化が可能。

ODEの配布ファイルには簡易的な可視化ツールとしてDrawStuffが含まれている。

要するにDrawStuffの処理はODEの処理に原則として(処理が重たくなると色々問題があるが)影響を及ぼさない。 逆に言えば注目したい物体だけ表示してトレースする事も可能。

物体の扱い方

Body, Geometryの単体でも存在は可能だが、必ず一緒に扱う方が間違いが少ない。 ただし、衝突計算とは無縁の存在を配置する場合はその限りではない。

ODEによるプログラムの組み立て方

  1. 全体の処理に関する設定
  2. 空間(World,Space)の作成、設定
  3. 物体の作成
  4. Jointの作成
  5. コールバックの記述(衝突、キー入力)
  6. Simulationループ
  7. 終了処理

dMassの重要性

  • 重量パラメータ
    • ボディの全質量
    • ボディ座標系での重心位置
    • ボディ座標系での慣性テンソル
  • dMassSetを使う
    • dMassSetTotalは全体の重さを指定
    • dMassSetは密度を指定
    • dMassは使い回し可

物体作成テンプレ

typedef struct {   
  dBodyID body;     
  dGeomID geom;     
  dReal r, m; 
  dReal red,green,blue;
} MyObject;

とでもして

MyObject xxx;
dMass mass;
xxx.body = dBodyCreate(world); //worldはdWorldCreate()で作成
dBodySetPosition(xxx.body,x,y,z); //x,y,zは位置
dMassSetZero(&mass);
//dMassSetXXX(&mass,d,大きさ); //XXXは形状, dは密度
dMassSetXXXTotal(&mass,weight,大きさ); //weightは全体の重さ
dBodySetMass(xxx.body,&mass);
xxx.geom = dCreateXXX(大きさ); //大きさはMassと合わせる
dGeomSetBody(xxx.geom, xxx.body); //GeometryとBodyを関連付ける

を一つのルーチンにしておく。

全ての物体にGeometryを持たせる弊害

衝突が増えることにより、処理が増える。そこで、衝突検出の際に処理が不要な物体については間引くことで対処する。

衝突検出コールバックでの間引き処理

  1. dGeomID o1とdGeomID o2が何であるかを知る
  2. 処理が不要な場合はreturn(固定物体が陸上に置かれている場合等)
  3. 物体ごとに接触ジョイント処理

とする。 これにより衝突したケースごとに条件を変えることも可能になる。 但し、物体AとBの衝突をフックしたい場合には、o1がAでo2がBの場合だけでなくo1がBでo2がAとなる可能性もあることを加味してコーディングする。

間引き処理の一例

細かい話は後程。ちなみにここではdAreConnectedExcluding()は使っていない。

なお、enumでPRINT_CONTACT=1にするとデバッグ情報を表示する。

    static void nearCallback(void *data, dGeomID o1, dGeomID o2)
    {
      int i;
      const int N = 10;
      dContact contact[N];
      //Ground同士なら2、1ならボールと
      int isGround=0;
      isGround = (ground == o1) + (obon.geom == o1) + (ground == o2) + (obon.geom == o2);

      int isEdge=0;
      for (i=0; i<nEdges; i++){
        if (edges[i].geom == o1){
          isEdge++;
        }
        if (edges[i].geom == o2){
          isEdge++;
        }
      }
      int isWall=0;
      for (i=0; i<nWalls; i++){
        if(walls[i].geom == o1){
          isWall++;
        }
        if(walls[i].geom == o2){
          isWall++;
        }
      }
      int isBall=0;
      for (i=0; i<nBalls; i++){
        if (ballp[i].geom == o1){
          isBall++;
        }
        if (ballp[i].geom ==o2){
          isBall++;
        }
      }
      int isGoal= 0;
      isGoal=((goal.geom == o1) || (goal.geom == o2));


      int n =  dCollide(o1, o2, N, &contact[0].geom, sizeof(dContact));
      if(isBall>1){ //ボールの衝突を別のContactにしてみる
        if(PRINT_CONTACT){
          fprintf(stderr,"ball and ball\n");
        }
        for (int i = 0; i < n; i++) {
          contact[i].surface.mode = dContactBounce;
          contact[i].surface.bounce = 0.001; 
          contact[i].surface.bounce_vel = 0.5;
          dJointID c = dJointCreateContact(world, contactgroup, &contact[i]);
          dJointAttach(c,dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2));
        }
      }else if(isBall ==1 && isWall == 1){//ボールと壁
        if(PRINT_CONTACT){
         fprintf(stderr,"ball and wall\n");
        }
        for (int i = 0; i < n; i++) {
          contact[i].surface.mode = dContactBounce;
          contact[i].surface.bounce = 0.01; 
          contact[i].surface.bounce_vel = 0.2;
          contact[i].surface.mu =0.4;
          dJointID c = dJointCreateContact(world, contactgroup, &contact[i]);
          dJointAttach(c,dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2));
        }
      }else if((isBall ==1 && isGround == 1) || (isBall ==1 && isEdge==1)){ //ボールと迷路
        if(PRINT_CONTACT){
          fprintf(stderr,"ball and ground\n");
        }
        for (int i = 0; i < n; i++) {
          contact[i].surface.mode = dContactBounce;
          contact[i].surface.bounce = 0.001; 
          contact[i].surface.bounce_vel = 0.8;
          contact[i].surface.mu =0.4;
          dJointID c = dJointCreateContact(world, contactgroup, &contact[i]);
          dJointAttach(c,dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2));
        }
      }else if(isGoal == 1 && isBall == 1){
        if(PRINT_CONTACT){
          fprintf(stderr,"ball and goal\n");
        }
        if (isBallOnGoal == 0){
          //ゴール処理
          isBallOnGoal = 1;
        }
        for (int i = 0; i < n; i++) {
          contact[i].surface.mode = dContactBounce;
          contact[i].surface.bounce = 0.001; 
          contact[i].surface.bounce_vel = 0.8;
          contact[i].surface.mu =0.4;
          dJointID c = dJointCreateContact(world, contactgroup, &contact[i]);
          dJointAttach(c,dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2));
        }
      }else if((isWall ==1 && isGround == 1) || (isWall == 1 && isEdge == 1) ||
        isWall==2 || isEdge==2 || (isEdge==1 && isGround==1) || (isGoal == 1 && isWall) ||
        (isGoal == 1 && isGround == 1) || (isGoal==1 && isEdge==1)){ //無視項目
        return;
      } else {
        if(PRINT_CONTACT){
          fprintf(stderr,"設定漏れ\n");
        }
      }
    }

Jointの作成

JointIDはグローバル変数としておく。 ここでは便宜上

dJointID yyyJoint;
dJointID yyyMotor;

と宣言してあることにする。

固定Jointテンプレ

yyyJoint = dJointCreateFixed(world,0); //第二引数はdJointGroupIDで通常0
dJointAttach(yyyJoint, body1, body2); //body1とbody2をくっつける。位置関係は宣言した時の状態
dJointSetFixed(yyyJoint);

他のJointテンプレ

yyyJoint = dJointCreate(world,0);
dJointAttach(yyyJoint, body1, body2);
dJointSetYYYAnchor(中心点、軸などの設定); //YYYはジョイントの種類

Aモーターのテンプレ(作成した可動ジョイントに付加する型で宣言する)

yyyMotor = dJointCreateAMotor(world,0);
dJointAttach(yyyMotor, body1, body2);
dJointSetAMotorNumAxes(yyyMotor,3); //第二引数は軸数
//第二引数は軸番号(ここでは0-2)、第三引数はbody1と2のどっちに付けるか(0だとworld)。あとの3つは回転軸を示すベクトル
dJointSetAMotorAxis(yyyMotor, 0, 1, 0, 0, 1); 
dJointSetAMotorAxis(yyyMotor, 1, 2, 0, 1, 0);
dJointSetAMotorAxis(yyyMotor, 2, 2, 1, 0, 0);
dJointSetAMotorMode(yyyMotor, dAMotorEuler); //モーターの角度の指定方法

Aモーターの制御

  1. 何らかの手段で各軸の角度用の変数を変える(command()によるキー操作が主)

  2. dWorldStepの前に動かす

    dJointSetAMotorParam(yyyMotor, dParamVel,目標角速度); //第二引数は軸の指定 dJointSetAMotorParam(yyyMotor, dParamFMax, 最大トルク);

コールバックの記述

衝突検出

物体が衝突した際に呼ばれるコールバックで、処理が必要か否かを判別するのに便利な関数もある。

  • dNearCallback(void *data, dGeomID o1, dGeomID o2)
    • dAreConnectedExcluding(body1, body2, dJointTypeCOntact);
    • -body1とbody2がJoint結合なら1を返す。

発生した衝突は瞬時的にJointとして扱われる。

つづく