パーティクルの動きの改良に加え、ソースコードもリファクタリングされて品質が上がっています。さすが!
さてさて、今日は Processing を Twitter に組み合わせてへんなのを作りますよー∩( ・ω・)∩
なんか、テキスト領域に Twitter のユーザ名を入れると、フォローしているおともだちが3次元空間を漂うというなんとも愉快なスケッチです。
実は、ほとんど昨日のソースコードを流用しただけの手抜き更新というのは内緒だよ。
【Processing と GUI】
本日一発目のテーマは、Processing スケッチへの GUI コンポーネントの組み込みです。 とりあえず完成すると以下のようになります(少し見づらいですが、動画の下の方にある藍色の矩形がテキストフィールドです)。
Processing 公式サイトにはいくつかのサードパーティ製ライブラリが紹介されています。僕はその中から、ControlP5 というライブラリを選びました(現時点の最新バージョンは ControlP5 0.5.4)。
OpenGL レンダラの環境でテキストフィールドを使用するためのミニマルなサンプルコードは以下の通り。なお、外部ライブラリの設置方法に関しては、うえちょこさんの記事に詳しく書かれています。
【ソースコード(展開してご覧ください)】
import processing.opengl.*; import controlP5.*; ControlP5 controlP5; Textfield tfUserName; // テキストフィールド void setup() { size(400, 300, OPENGL); controlP5 = new ControlP5(this); tfUserName = controlP5.addTextfield("userName", 10, 10, 200, 20); tfUserName.setFocus(true); } void draw() { hint(ENABLE_DEPTH_TEST); background(0); /* ============================== */ /* ここに描画用のコードを書きます */ /* ============================== */ // GUIコンポーネント描画のためにカメラ位置をリセット camera(); hint(DISABLE_DEPTH_TEST); } // テキストが入力された際にコールバックされるメソッド public void userName(String txt) { if(txt.trim().length() < 1) return; println("「" + txt + "」が入力されました"); }
デモ動画のソースコードは……適当に作ったため、あまり見せられる代物ではありませんが、たぶん昔の僕だったら「汚くてもいいからソースコードを知りたい」と思ったでしょうから、こっそり晒しておきますね。
【おまけソースコード(展開してご覧ください)】
import processing.opengl.*; import javax.media.opengl.*; import controlP5.*; final int FIELD_SIZE = 1000; final int FIELD_STEP = 100; ControlP5 controlP5; Textfield tfUserName; // テキストフィールド PFont font; long time; List<Message> messageList; // メッセージリスト void setup() { size(640, 480, OPENGL); messageList = new LinkedList<Message>(); controlP5 = new ControlP5(this); tfUserName = controlP5.addTextfield("textfield", 10, 10, 200, 20); tfUserName.setFocus(true); font = createFont("Meiryo", 48); } void draw() { background(0); PGraphicsOpenGL pgl = (PGraphicsOpenGL)g; // カメラの設定 float angle = 0.5f * radians(time); camera(300*cos(angle), -300*sin(angle), 300*sin(angle), 0, 0, 0, 0, 1, 0); // 地面の描画 pushStyle(); stroke(255, 0, 0); for(int i = -FIELD_SIZE; i <= FIELD_SIZE; i += FIELD_STEP) { line( i, 0, -FIELD_SIZE, i, 0, FIELD_SIZE); line(-FIELD_SIZE, 0, i, FIELD_SIZE, 0, i); } popStyle(); // α合成を有効にする GL gl = pgl.beginGL(); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glDisable(GL.GL_DEPTH_TEST); pgl.endGL(); // メッセージの描画 for(Iterator iter = messageList.iterator(); iter.hasNext();) { Message msg = (Message)iter.next(); // 更新して死んでたらリストから削除 if(!msg.update()) iter.remove(); } time++; // GUI コンポーネント描画のための設定 camera(); gl = pgl.beginGL(); gl.glDisable(GL.GL_DEPTH_TEST); // 深度テストを無効化 gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); pgl.endGL(); } // テキストが入力された際にコールバックされるメソッド public synchronized void textfield(String txt) { // 適当な初期位置を指定して、リストに新たなメッセージを追加する messageList.add(new Message(txt, new PVector(random(-100, 100), 0, random(-100, 100)))); } // メッセージクラス class Message { private final int BASECOLOR = 0xFFFFFFFF; // 基本色 private final int TTL = 200; // 生存時間 private final float dY = -0.5; // y座標の増分 private String message; // メッセージ private PVector pos; // 位置 private int time; // 経過時間 // コンストラクタ // ----------------------------- // メッセージと初期位置を指定する Message(String msg, PVector pos) { this.message = msg; this.pos = pos; } // update() // ----------------------------- // 状態を更新し、生死状態を返す // true: 有効(生きている) // false: 無効(死んでいる) boolean update() { if(++time > TTL) return false; pos.y += dY; // ビルボーディング pushMatrix(); translate(pos.x, pos.y, pos.z); // 物体を並進(ローカル座標系) // モデル-ビュー行列を取得 PMatrix3D builboardMat = (PMatrix3D)g.getMatrix(); // 回転成分を単位行列に builboardMat.m00 = builboardMat.m11 = builboardMat.m22 = 1; builboardMat.m01 = builboardMat.m02 = builboardMat.m10 = builboardMat.m12 = builboardMat.m20 = builboardMat.m21 = 0; resetMatrix(); applyMatrix(builboardMat); // アルファ値を計算 pushStyle(); int a = 255 - 255 * time / TTL; fill(a << 24 | BASECOLOR & 0xFFFFFF); // フォントを設定 textFont(font, 24); textAlign(CENTER); // テキストを描画 text(message, 0, 0); popStyle(); popMatrix(); return true; } }
【Processing と Twitter】
GUI は一旦おいといて(つ´∀`)つ
今度は Twitter との連繋を考えてみます。ここでのゴールは、ひとまず Twitter でフォローしている方々のアイコンを 3次元空間中に散りばめる事です。
Twitter API を直叩きするのも面倒なので、Twitter4J という便利なライブラリをちょこっと使います。ライブラリは公式サイト あるいは GitHub から入手できます。今回は最新安定版である twitter4j-2.2.5.zip を選択しました。
今日はもう疲れ始めたので、OAuth 認証の要らないフォロー/フォロワー一覧を取得するところまでやろうかと思います。
JavaDoc などを参照しつつ、フレンド(自分がフォローしているアカウント) を調べるプログラムを Processing で書いてみました。
【ソースコード(展開してご覧ください)】
import twitter4j.*; final String USER_NAME = "tercel_s"; Twitter twitter; void setup() { twitter = new TwitterFactory().getInstance(); try { // 指定したユーザがフォローしているアカウント一覧を取得 IDs friends = twitter.getFriendsIDs(USER_NAME, -1); long[] friendsIDs = friends.getIDs(); // Twitter ID からユーザ情報を逐次取得して表示 for(long id : friendsIDs) { User user = twitter.showUser(id); println(user.getName() + " (" + user.getScreenName() + ")"); } } catch (TwitterException ex) { if(ex.isCausedByNetworkIssue()) { // 何らかの事情でネットワークに接続できませんでした } else { int statusCode = ex.getStatusCode(); switch(statusCode) { case 400: // Bad Request // リクエストに不正があるかレートリミットに達しました break; case 401: // Unauthorized // 認証情報に間違いがあります break; case 403: // Forbidden // リクエストが拒否されました break; case 404: // Not Found // 存在しないリソースにアクセスしました break; case 500: // Server Internal Error // サーバ障害が発生しています break; case 502: // Bad Gateway // Twitterサービスに障害が発生しています break; case 503: // Service Unavailable // Twitterが過負荷状態です break; } } } noLoop(); } void draw() { // }
実行すると、コンソールにフレンド一覧が列挙されます。
ただこれ、けっこう時間がかかります。ですので、フォロー一覧の読み込み処理は別スレッドに移した方がよいでしょう。適当にマルチスレッド化するとこうなります(本当に適当なので、よい子はマネしないでね)。
【ソースコード(展開してご覧ください)】
import twitter4j.*; final String USER_NAME = "tercel_s"; Twitter twitter; boolean isLoading; void setup() { twitter = new TwitterFactory().getInstance(); // マルチスレッドだよ∩( ・ω・)∩ new Thread() { public synchronized void run() { isLoading = true; try { // 指定したユーザがフォローしているアカウント一覧を取得 IDs friends = twitter.getFriendsIDs(USER_NAME, -1); long[] friendsIDs = friends.getIDs(); // Twitter ID からユーザ情報を逐次取得して表示 for(long id : friendsIDs) { User user = twitter.showUser(id); println(user.getName() + " (" + user.getScreenName() + ")"); } isLoading = false; } catch (TwitterException ex) { /* 省略 */ } finally { isLoading = false; // デッドロック回避用 } } }.start(); } void draw() { // }
これさえできれば、デモ動画の作品を作るのは簡単です。ほとんど昨日のパーティクルをユーザのアイコンに差し換えただけです。
【おまけソースコード(展開してご覧ください)】
import processing.opengl.*; import javax.media.opengl.*; import twitter4j.*; final int FIELD_SIZE = 1000; final int FIELD_STEP = 100; final String USER_NAME = "tercel_s"; boolean isLoading; List<Agent> agentList; // フレンドアイコンのリスト long time; void setup() { size(800, 600, OPENGL); agentList = new ArrayList<Agent>(); // マルチスレッドでフレンドを読み込む new Thread(new FriendLoader()).start(); hint(ENABLE_DEPTH_SORT); } void draw() { background(0); /* ================== */ /* カメラを適当に設定 */ /* ================== */ float angle = 0.05f * radians(time); camera(800, -200, 0, 0, 0, 0, 0, 1, 0); rotateY(angle); // ちょっと回す pushStyle(); // テクスチャの加算合成を有効にする PGraphicsOpenGL pgl = (PGraphicsOpenGL)g; GL gl = pgl.beginGL(); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); pgl.endGL(); noStroke(); fill(0); // フレンド一覧を表示 for(int i = 0; i < agentList.size(); ++i) { agentList.get(i).update(); } popStyle(); /* ============== */ /* 地面とかの描画 */ /* ============== */ pushStyle(); // 地面の描画 stroke(0, 255, 0, 100); for(int i = -FIELD_SIZE; i <= FIELD_SIZE; i += FIELD_STEP) { line( i, 0, -FIELD_SIZE, i, 0, FIELD_SIZE); line(-FIELD_SIZE, 0, i, FIELD_SIZE, 0, i); } popStyle(); time++; mm.addFrame(); if(time > 60 * 60) { mm.finish(); background(0); noLoop(); } } // フレンドを読み込むためのクラス class FriendLoader implements Runnable { public void run() { isLoading = true; Twitter twitter = new TwitterFactory().getInstance(); try { // まずは自分自身をリストに追加 User me = twitter.showUser(USER_NAME); PImage myIcon = loadImage(me.getProfileImageURL().toString()); myIcon.resize(100, 100); agentList.add(new Agent(myIcon, new PVector())); // 自分がフォローしているアカウント一覧を取得 IDs friends = twitter.getFriendsIDs(USER_NAME, -1); long[] friendsIDs = friends.getIDs(); // Twitter ID からユーザ情報を逐次取得 for(long id : friendsIDs) { User user = twitter.showUser(id); // アイコン取得とリサイズ PImage profileImage = loadImage(user.getProfileImageURL().toString()); if(profileImage == null) continue; profileImage.resize(100, 100); // 位置の設定 float x = random(-1000, 1000); float z = random(-1000, 1000); agentList.add(new Agent(profileImage, new PVector(x, 0, z))); } isLoading = false; } catch (TwitterException ex) { /* 省略 */ } finally { isLoading = false; // デッドロック回避用 } } } // エージェント class Agent { private final float TERRITORY_SIZE = 2000; private final float NOISE_SCALE = 0.001f; private final float AGENT_SIZE = 50; private PImage tex; // テクスチャ private PVector center; // 縄張りの中心座標 private PVector offset; // 中心からのオフセット private PVector pos; // 座標 private long time; // 経過時間 float xOffset, zOffset; // ノイズ生成用 Agent(PImage tex, PVector center) { this.center = center; this.tex = tex; pos = new PVector(); offset = new PVector(); xOffset = random(TERRITORY_SIZE); zOffset = random(TERRITORY_SIZE); } PVector getPos() { return pos; } void update() { if (tex == null) return; time++; offset.x = (noise((time + xOffset) * NOISE_SCALE) - 0.5f) * TERRITORY_SIZE; offset.z = (noise((time + zOffset) * NOISE_SCALE) - 0.5f) * TERRITORY_SIZE; pushMatrix(); pos.set(center.x + offset.x, center.y, center.z + offset.z); translate(pos.x, pos.y, pos.z); // モデル-ビュー行列を取得 PMatrix3D builboardMat = (PMatrix3D)g.getMatrix(); // 回転成分を単位行列に builboardMat.m00 = builboardMat.m11 = builboardMat.m22 = 1; builboardMat.m01 = builboardMat.m02 = builboardMat.m10 = builboardMat.m12 = builboardMat.m20 = builboardMat.m21 = 0; resetMatrix(); applyMatrix(builboardMat); beginShape(QUADS); texture(tex); vertex(-0.5f * AGENT_SIZE, -AGENT_SIZE, 0, 0, 0); vertex(-0.5f * AGENT_SIZE, 0, 0, 0, tex.height-1); vertex( 0.5f * AGENT_SIZE, 0, 0, tex.width, tex.height-1); vertex( 0.5f * AGENT_SIZE, -AGENT_SIZE, 0, tex.width, 0); endShape(); popMatrix(); } }
あとはこの 2 つを適当に組み合わせれば、冒頭の動画のようなものが作れると思います。
なしくずしてきにめでたしめでたし。
そんなこんなで久しぶりに Twitter ネタを書きましたが、このプログラムにはまだまだアラがあります。
まず、Twitter の API 呼び出しには制限が設けられており、このプログラムを試しているとあっという間に API 上限に引っかかってしまいます。フォローしているアカウントの情報を一人ずつ取得していくと、アホみたいなスピードで限界に近付いていきます。
次に、計算機資源の問題があります。今はとりあえずフレンド情報を全読みしていますが、それこそ何十万人もフォローしているような人が動かしたら、あっという間にメモリを喰い尽くして死ぬでしょう。
それでもまぁ、とりあえず Twitter と P5 をくっつけてそれっぽい事ができたので今日はよしとします。ふぅ、つかれた(´-ω-`)
おもしろいな!
返信削除すごい!自分もやってみたい!
返信削除