パーティクルの動きの改良に加え、ソースコードもリファクタリングされて品質が上がっています。さすが!
さてさて、今日は 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 をくっつけてそれっぽい事ができたので今日はよしとします。ふぅ、つかれた(´-ω-`)
おもしろいな!
返信削除すごい!自分もやってみたい!
返信削除