2011/06/06

くだけちるエフェクト(改訂篇)

先月作ったくだけちるエフェクトの改良版。

破片の形状生成アルゴリズムを Delaunay 分割に変更し、さらに各破片の飛び散り方を 3 次元的にしてみた。

前回と違ってクリック時にフラッシュ効果を入れていないのは、そんなごまかしをしなくても割とまともに見られるようになったからである。



【ソースコード】

このサンプルがサポートするのは、以下の Scene インタフェースを実装した状態クラス間の遷移のみである。

// **************************************
// シーンを司るインタフェースクラス
// 
// updateメソッドをオーバーライドして使う
// 戻り値は、次に遷移するシーンオブジェクト
// (遷移しない場合はthisポインタ)
// **************************************
interface Scene {
  Scene update();
  void setEnable(boolean isEnable);  
}

で。

肝心の状態遷移効果に関する処理は、先日の Delaunay 分割を加えてこんな感じになった。

// ∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴
// 
// 状態遷移に関するクラス群
//
// ∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵


// **************************************
//
// 画面遷移用クラス
//
// **************************************
class StateTransitionEffect implements Scene {

  // ======================================
  // 遷移にかかる時間と、
  // フェード効果発現までの時間(変更可能)
  // ======================================
  private final int MAX_TIME   = 90;
  private final int FADE_DELAY = 80;

  private PImage nextSceneImage;
  private Fragments fragments;
  private int time;
  private Scene cur, next;
  
  // ======================================
  // コンストラクタ
  // 
  // Scene
  //  - cur  : 前状態
  //  - next : 次状態
  // ======================================
  StateTransitionEffect(Scene cur, Scene next) {
    this.cur = cur;
    this.next = next;
    
    nextSceneImage = createImage(width, height, RGB);

    cur.setEnable(false);
    next.setEnable(false);
    
    fragments = new Fragments(50);
    time = 0;
  }
  
  Scene update(){
    camera();
    noStroke();
    next.update();
    
    int a;  // アルファ値(フェード効果で使用)
    
    loadPixels();
    for(int i = 0; i < pixels.length; i++)
      nextSceneImage.pixels[i] = pixels[i];
    background(nextSceneImage);
    
    // 次状態のフェードイン効果
    /* --------------------------------------------------
    a = (int)(255 * (float)(MAX_TIME - time) / MAX_TIME);
    fill(0, a);    noSmooth();
    rect(0, 0, width, height);
    -------------------------------------------------- */
    
    // 破片の描画
    fragments.update();
    
    // 破片のフェードアウト効果
    if (FADE_DELAY <= time) {
      a = (int)(255*(float)(time-FADE_DELAY)/(MAX_TIME-FADE_DELAY));

      loadPixels();
      // アルファ合成
      for(int i = 0; i < pixels.length; i++) {
        int fg = nextSceneImage.pixels[i];
        int bg = pixels[i];
        
        int fR = (0x00FF0000 & fg) >>> 16;
        int fG = (0x0000FF00 & fg) >>> 8;
        int fB =  0x000000FF & fg;
      
        int bR = (0x00FF0000 & bg) >>> 16;
        int bG = (0x0000FF00 & bg) >>> 8;
        int bB =  0x000000FF & bg;
      
        int rR = (((fR - bR) * a) >>> 8 ) + bR;
        int rG = (((fG - bG) * a) >>> 8 ) + bG;
        int rB = (((fB - bB) * a) >>> 8 ) + bB;
        
        pixels[i] = 0xFF000000 | (rR << 16) | (rG << 8) | rB;
      }
      updatePixels();
    }
    
    if(time++ < MAX_TIME) return this;
    
    next.setEnable(true);
    return next;
  }
  
  void setEnable(boolean isEnable) { 
    /* なにもしない */
  }
}

// **************************************
//
// 破片の集合クラス
//
// **************************************
class Fragments {
  PImage tex;
  List<Fragment> f;
  
  Fragments(int nPoints) {
    tex = createImage(width, height, RGB);
    loadPixels();
    for(int i = 0; i < pixels.length; i++) {
      tex.pixels[i] = pixels[i];
    }
    
    // 破片セット
    f = new ArrayList<Fragment>();
    
    // Delaunay分割
    List<PVector> pList = new ArrayList<PVector>();
    pList.add(new Point(0, 0));
    pList.add(new Point(width, 0));
    pList.add(new Point(width, height));
    pList.add(new Point(0, height));
    for(int i = 0; i < nPoints; i++) {
      pList.add(new Point(random(width), random(height)));
    }
    DelaunayTriangles delaunay = new DelaunayTriangles(pList);
    
    // Delaunay三角集合をリスト化
    HashSet tSet = delaunay.getDelaunayTriangulationSet();
    for(Iterator it = tSet.iterator(); it.hasNext();) {
      f.add(new Fragment((Triangle)it.next(), tex));
    }
  }
  
  void update() {
    for(Iterator it = f.iterator(); it.hasNext();) {
      Fragment fragment = (Fragment)it.next();
      fragment.update();
    }
  }
}

// **************************************
//
// 破片クラス
//
// **************************************
class Fragment {
  // PVector[] texCoords;
  PVector[] vertices;
  PImage tex;  // テクスチャ
  
  // 並進成分・回転角
  private float x, y, z, rx, ry, rz;
  private float dx, dy, dz, drx, dry, drz;
  private float ddx, ddy, ddz;
  
  // 与えられた三角形の重心
  private float cx, cy, cz;
  
  Fragment(Triangle t, PImage tex) {
    this.tex = tex;
    
    // 頂点座標をセット
    vertices = new PVector[3];
    vertices[0] = t.p1;
    vertices[1] = t.p2;
    vertices[2] = t.p3;
    
    // 重心を計算
    cx = (t.p1.x + t.p2.x + t.p3.x)/3.0;
    cy = (t.p1.y + t.p2.y + t.p3.y)/3.0;
    cz = (t.p1.z + t.p2.z + t.p3.z)/3.0;
    
    // 並進・回転の初期値を設定
    x = y = z = 0;
    rx = ry = rz = 0;
    
    // ======================================
    // 速度・加速度を設定(変更可能)
    // ======================================
    dx =  random(-0.5, 0.5);
    dy = -random( 1  , 6  );
    dz =  random(-0.5, 0.5);
    
    ddx = 0;
    ddy = random(0.2, 0.5);
    ddz = 0;

    drx = radians(random(-2, 2));
    dry = radians(random(-2, 2));
    drz = radians(random(-2, 2));    
  }
  
  void update() {
    noStroke();
    fill(255);
    lights();

    pushMatrix();
    // 並進
    translate(cx + x, cy + y, cz + z);
    
    // 回転  
    rotateX(rx); rotateZ(rz); rotateY(ry);
    
    // 並進
    translate(-cx, -cy, -cz);

    textureMode(IMAGE);
    beginShape(TRIANGLES);
    texture(tex);
    for(int i = 0; i < vertices.length; i++) {
      vertex(vertices[i].x, vertices[i].y,
             vertices[i].x, vertices[i].y);
    }
    endShape();
    popMatrix();
    noLights();
    
    x  += dx;  y  += dy;  z  += dz;
    rx += drx; ry += dry; rz += drz;
    dx += ddx; dy += ddy; dz += ddz;
  }
}


// ∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴
// 
// Delaunay分割に関するクラス群
//
// ∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵∴∵

// **************************************
//
// Delaunay分割用クラス
//
// **************************************

class DelaunayTriangles {
  private List<PVector> pointList;
  // ======================================
  // コンストラクタ
  // 与えられた点のリストを基にDelaunay分割を行う
  // ======================================
  public DelaunayTriangles(List<PVector> pointList) {
    this.pointList = pointList;
  }
  
  
  // ======================================
  // Delaunay三角分割を行う
  // ======================================
  public HashSet<Triangle>
      getDelaunayTriangulationSet() {
    
    // 三角形リストを初期化
    HashSet<Triangle> triangleSet = new HashSet<Triangle>();
    
    // 巨大な外部三角形をリストに追加
    Triangle hugeTriangle = getHugeTriangle();
    triangleSet.add(hugeTriangle);

    try {
      // --------------------------------------
      // 点を逐次添加し、反復的に三角分割を行う
      // --------------------------------------
      for(Iterator pIter = pointList.iterator(); pIter.hasNext();) {
        Object element = pIter.next();
        Point p = element instanceof Point ? 
            (Point)element : new Point((PVector)element);
        
        // --------------------------------------
        // 追加候補の三角形を保持する一時ハッシュ
        // --------------------------------------
        // 追加候補の三角形のうち、「重複のないものだけ」を
        // 三角形リストに新規追加する
        //          → 重複管理のためのデータ構造
        // tmpTriangleSet
        //  - Key   : 三角形
        //  - Value : 重複していないかどうか
        //            - 重複していない : true
        //            - 重複している   : false
        // --------------------------------------
        HashMap<Triangle, Boolean> tmpTriangleSet 
          = new HashMap<Triangle, Boolean>();

        // --------------------------------------
        // 現在の三角形リストから要素を一つずつ取り出して、
        // 与えられた点が各々の三角形の外接円の中に含まれるかどうか判定
        // --------------------------------------
        for(Iterator tIter=triangleSet.iterator(); tIter.hasNext();){
          // 三角形リストから三角形を取り出して…
          Triangle t = (Triangle)tIter.next();
              
          // その外接円を求める。
          Circle c = getCircumscribedCirclesOfTriangle(t);
              
          // --------------------------------------
          // 追加された点が外接円内部に存在する場合、
          // その外接円を持つ三角形をリストから除外し、
          // 新たに分割し直す
          // --------------------------------------
          if (Point.dist(c.center, p) <= c.radius) {
            // 新しい三角形を作り、一時ハッシュに入れる
            addElementToRedundanciesMap(tmpTriangleSet,
              new Triangle(p, t.p1, t.p2));
            addElementToRedundanciesMap(tmpTriangleSet,
              new Triangle(p, t.p2, t.p3));
            addElementToRedundanciesMap(tmpTriangleSet,
              new Triangle(p, t.p3, t.p1));
            
            // 旧い三角形をリストから削除
            tIter.remove();            
          }
        }
        
        // --------------------------------------
        // 一時ハッシュのうち、重複のないものを三角形リストに追加 
        // --------------------------------------
        for(Iterator tmpIter = tmpTriangleSet.entrySet().iterator();
            tmpIter.hasNext();) {

          Map.Entry entry = (Map.Entry)tmpIter.next();
          Triangle t = (Triangle)entry.getKey();
          
          boolean isUnique = 
              ((Boolean)entry.getValue()).booleanValue();

          if(isUnique) {
            triangleSet.add(t);
          }
        }
      }
      
      // 最後に、外部三角形の頂点を削除
      for(Iterator tIter = triangleSet.iterator(); tIter.hasNext();){
        // 三角形リストから三角形を取り出して
        Triangle t = (Triangle)tIter.next();
        // もし外部三角形の頂点を含む三角形があったら、それを削除
        if(hugeTriangle.hasCommonPoints(t)) {
          tIter.remove();
        }
      }
      
      return triangleSet;
    } catch (Exception ex) {
      return null;
    }
  }

  // ======================================
  // 一時ハッシュを使って重複判定
  // hashMap
  //  - Key   : 三角形
  //  - Value : 重複していないかどうか
  //            - 重複していない : true
  //            - 重複している   : false
  // ======================================
  private void addElementToRedundanciesMap
      (HashMap<Triangle, Boolean> hashMap, Triangle t) {
    if (hashMap.containsKey(t)) {
      // 重複あり : Keyに対応する値にFalseをセット
      hashMap.put(t, new Boolean(false));
    } else {
      // 重複なし : 新規追加し、
      hashMap.put(t, new Boolean(true));
    }
  }
  
  // ======================================
  // 最初に必要な巨大三角形を求める
  // ======================================
  // 画面全体を包含する正三角形を求める
  private Triangle getHugeTriangle() {
    return getHugeTriangle(new PVector(0, 0), 
                           new PVector(width, height));    
  }
  // 任意の矩形を包含する正三角形を求める
  // 引数には矩形の左上座標および右下座標を与える
  private Triangle getHugeTriangle(PVector start, PVector end) {
    // start: 矩形の左上座標、
    // end  : 矩形の右下座標…になるように
    if(end.x < start.x) {
      float tmp = start.x;
      start.x = end.x;
      end.x = tmp;
    }
    if(end.y < start.y) {
      float tmp = start.y;
      start.y = end.y;
      end.y = tmp;
    }
    
    // 1) 与えられた矩形を包含する円を求める
    //      円の中心 c = 矩形の中心
    //      円の半径 r = |p - c| + ρ
    //    ただし、pは与えられた矩形の任意の頂点
    //    ρは任意の正数
    Point center = new Point( (end.x - start.x) / 2.0,
                              (end.y - start.y) / 2.0 );
    float radius = Point.dist(center, start) + 1.0;
    
    // 2) その円に外接する正三角形を求める
    //    重心は、円の中心に等しい
    //    一辺の長さは 2√3・r
    float x1 = center.x - sqrt(3) * radius;
    float y1 = center.y - radius;
    Point p1 = new Point(x1, y1);
    
    float x2 = center.x + sqrt(3) * radius;
    float y2 = center.y - radius;
    Point p2 = new Point(x2, y2);
    
    float x3 = center.x;
    float y3 = center.y + 2 * radius;
    Point p3 = new Point(x3, y3);

    return new Triangle(p1, p2, p3);    
  }
  
  // ======================================
  // 三角形を与えてその外接円を求める
  // ======================================
  private Circle getCircumscribedCirclesOfTriangle(Triangle t) {
    // 三角形の各頂点座標を (x1, y1), (x2, y2), (x3, y3) とし、
    // その外接円の中心座標を (x, y) とすると、
    //     (x - x1) * (x - x1) + (y - y1) * (y - y1)
    //   = (x - x2) * (x - x2) + (y - y2) * (y - y2)
    //   = (x - x3) * (x - x3) + (y - y3) * (y - y3)
    // より、以下の式が成り立つ
    //
    // x = { (y3 - y1) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1)
    //     + (y1 - y2) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1)} / c
    //
    // y = { (x1 - x3) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1)
    //     + (x2 - x1) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1)} / c
    //
    // ただし、
    //   c = 2 * {(x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)}
    
    float x1 = t.p1.x;
    float y1 = t.p1.y;
    float x2 = t.p2.x;
    float y2 = t.p2.y;
    float x3 = t.p3.x;
    float y3 = t.p3.y;
    
    float c = 2.0 * ((x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1));
    float x = ((y3 - y1) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1)
             + (y1 - y2) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1))/c;
    float y = ((x1 - x3) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1)
             + (x2 - x1) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1))/c;
    Point center = new Point(x, y);
    
    // 外接円の半径 r は、半径から三角形の任意の頂点までの距離に等しい
    float r = Point.dist(center, t.p1);
    
    return new Circle(center, r);
  }
}

// **************************************
//
// 三角形クラス
//
// **************************************

class Triangle{
  public Point p1, p2, p3;  // 頂点
  
  // ======================================
  // コンストラクタ
  // 3頂点を与えて三角形をつくるよ
  // 頂点はPointで与えてもOK
  // ======================================
  public Triangle(PVector p1, PVector p2, PVector p3){
    this.p1 = p1 instanceof Point ? (Point)p1 : new Point(p1);
    this.p2 = p2 instanceof Point ? (Point)p2 : new Point(p2);
    this.p3 = p3 instanceof Point ? (Point)p3 : new Point(p3);
  }
  
  // ======================================
  // 同値判定
  // ======================================
  public boolean equals(Object obj) {
    try {
      Triangle t = (Triangle)obj;
      // ※ 同値判定に頂点を用いると、
      // 三角形の頂点の順番を網羅的に考慮する分条件判定が多くなる。
      return(p1.equals(t.p1) && p2.equals(t.p2) && p3.equals(t.p3) ||
             p1.equals(t.p2) && p2.equals(t.p3) && p3.equals(t.p1) ||
             p1.equals(t.p3) && p2.equals(t.p1) && p3.equals(t.p2) ||
              
             p1.equals(t.p3) && p2.equals(t.p2) && p3.equals(t.p1) ||
             p1.equals(t.p2) && p2.equals(t.p1) && p3.equals(t.p3) ||
             p1.equals(t.p1) && p2.equals(t.p3) && p3.equals(t.p2) );
    } catch (Exception ex) {
      return false;
    }
  }
    
  // ======================================
  // ハッシュ表で管理できるよう、hashCodeをオーバーライド
  // ======================================
  public int hashCode() {
    return 0;
  }
  
  // ======================================
  // 他の三角形と共有点を持つか
  // ======================================  
  public boolean hasCommonPoints(Triangle t) {
    return (p1.equals(t.p1) || p1.equals(t.p2) || p1.equals(t.p3) ||
            p2.equals(t.p1) || p2.equals(t.p2) || p2.equals(t.p3) ||
            p3.equals(t.p1) || p3.equals(t.p2) || p3.equals(t.p3) );
  }
}

// **************************************
//
// 円クラス
//
// **************************************

class Circle {
  // 中心座標と半径
  Point center;
  float radius;
  
  // ======================================
  // コンストラクタ
  // 中心座標と半径を与えて円をつくるよ
  // ======================================
  public Circle(PVector c, float r){
    this.center = c instanceof Point ? (Point) c : new Point(c);
    this.radius = r;
  }
}

// **************************************
//
// 点クラス
//
// **************************************

class Point extends PVector {
  // ======================================
  // コンストラクタ
  // ======================================
  public Point() {
    super();
  }
  public Point(float x, float y) {
    super(x, y);
  }
  public Point(float x, float y, float z) {
    super(x, y, z);
  }
  public Point(PVector v) {
    this.x = v.x;
    this.y = v.y;
    this.z = v.z;
  }
  
  // ======================================
  // 同値判定
  // ======================================
  public boolean equals(Object o) {
    boolean retVal;
    try {
      PVector p = (PVector)o;
      return (x == p.x && y == p.y && z == p.z);
    } catch (Exception ex) {
      return false;
    }
  }
}

サンプルでは、デモ用の状態クラスをひとつ作り、Scene1 と名付けている。

テスト用のコードは以下の通り。

Scene scene;

void setup() {
  size(400, 300, P3D);
  scene = new Scene1();
}
void draw() {
  scene = scene.update();
}

// =======================================================
// デモ用の状態クラス
// =======================================================
class Scene1 implements Scene {
  float h, p, b;     // ヘディング・ピッチ・バンク回転量
  float dh, dp, db;  // 角速度
  int objColor, bgColor;
  boolean isEnable;
  

  Scene1() {
    // 色の付け方はてきとうです。
    objColor = color(random(100, 200), 
      random(100, 200), 
      random(100, 200));
    bgColor = color( red(objColor) + 55, green(objColor) + 55, blue(objColor) + 55);
    
    // 回転量・並進量を設定
    h = random(0, 360);
    p = random(-90, 90);
    b = random(0, 360);
    dh = random(-1, 1);
    db = random(-1, 1);
    dp = random(-1, 1);
    
    isEnable = true;
  }
  
  Scene update() {
    camera();
    lights();
    
    background(bgColor);
    
    noStroke();
    fill(objColor);
    
    pushMatrix();
    translate(width/2, height/2, -width/2);
    rotateY(radians(h));
    rotateX(radians(p));
    rotateZ(radians(b));
    box(250);
    popMatrix();
    
    h += dh;
    p += dp;
    b += db;
    
    noLights();
        
    if (mousePressed && isEnable) {
      return new StateTransitionEffect(this, new Scene1());
    } else {
      return this;
    }
  }
  
  void setEnable(boolean isEnable) {
    this.isEnable = isEnable;
  }
}

0 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)