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