Box2D.jsで円をゴムまりの様に衝撃で変形させるサンプルです。
ボールが伸びる長さは同じ時間に一定ではなくて一番大きい通常時の大きさの20%ずつ大きくなります。
var radius = this.circle.GetRadius() + (this.diffExpanding() * this.transformingSpeed);
18 Jan 2014
16 Jan 2014
Box2Dのworldを壁で囲むサンプル
Box2Dでworldを壁で囲むクラス worldWallsのサンプルです。
箱を作ります
第3引数のtrblはtop right bottom leftの略でバイナリ形式の文字列でどの辺に壁が欲しいかを指定します。 例えば 底辺にだけ壁が欲しい時は'0010'、上以外に壁が欲しい場合は'0111'を指定してください。
クラス外部からwallを参照したい場合は以下の様にプロパティにアクセスしてください
var walls = new worldWalls(debugDraw.GetDrawScale(), canvas, '0111');
walls.top.restition = 10; //右辺だけ強くバウンドするようにする。
箱を作ります
var box = new worldWalls(debugDraw.GetDrawScale(), canvas, '1111');worldBoxにscale(Box2D上の単位)とcanvas element(画面上の実寸を保持している)を渡しサイズを決定します。
第3引数のtrblはtop right bottom leftの略でバイナリ形式の文字列でどの辺に壁が欲しいかを指定します。 例えば 底辺にだけ壁が欲しい時は'0010'、上以外に壁が欲しい場合は'0111'を指定してください。
クラス外部からwallを参照したい場合は以下の様にプロパティにアクセスしてください
var walls = new worldWalls(debugDraw.GetDrawScale(), canvas, '0111');
walls.top.restition = 10; //右辺だけ強くバウンドするようにする。
Labels:
Box2D.js
5 Jan 2014
ProcessingでSocial Graphを作る
ProcessingでSocail Graphを作ってみました。自分とその友達同士の共通の友人の数を線の太さに表したソーシャルグラフです。
世にいろんなソーシャルグラフのライブラリがあるのでそれらを使えば一瞬なんだろうけど幾何学的なことを理解したくて、本を読みながら部品から一個一個全部作りました。詳しくは書かないけれどこんな感じのテクニックを使っています。
# コントロール的なロジックはSocialGraphというclassに集約されていて、引数はcsvファイルと、半径です。
SocialGraph(SocialGraphCsv csv, int radius)
こういうCSVを渡すとカラム数を拾って自動的に正N角形を描写して、人名や線を描写してくれます。
総当たり(隣接配列)のため下半分は上半分の繰り返しであるため全て0です。1は共通の友人が私しかいないことを意味しています。
共通の友人の数は以下のURLで取得できます
https://www.facebook.com/<person A>?and=<person B>
# クラス構成
# ソースコード
まだ整えられそう。ProcessingでOOPする難しさなんかも過去の記事にまとめました。
<script src="https://gist.github.com/suzukimilanpaak/8264451.js"></script>
# 感想
java(Processing Language)を久しぶりに書いたけど、今更OOPを気軽にやれない感じだった。ProcessingでOOPしようとするのが結構めんどくさい(不可能ではない)。ので再利用制のあるコードを書いて共有するのがProcessingの世界では手間。Javaの最近の文法を知らないけど、あの言語にあるこんなしようを使えばもっと奇麗になるかも。
ソーシャルグラフは 三角関数とπの組み合わせしか使わないので意外と簡単。
世にいろんなソーシャルグラフのライブラリがあるのでそれらを使えば一瞬なんだろうけど幾何学的なことを理解したくて、本を読みながら部品から一個一個全部作りました。詳しくは書かないけれどこんな感じのテクニックを使っています。
- 正N角形
- 三角関数 cos, sin
- ベジェ曲線
# コントロール的なロジックはSocialGraphというclassに集約されていて、引数はcsvファイルと、半径です。
SocialGraph(SocialGraphCsv csv, int radius)
こういうCSVを渡すとカラム数を拾って自動的に正N角形を描写して、人名や線を描写してくれます。
Tatsuya Suzuki | Emi Ito | Bruno Barroso | Mattew Day | Maxim Dokhlov | Mehmet Bagbekleyen | Alex Yixnitsky | Stuart Gardner | Derrick May | |
Tatsuya Suzuki | 0 | 50 | 19 | 10 | 9 | 11 | 15 | 2 | 0 |
Emi Ito | 0 | 0 | 2 | 3 | 3 | 2 | 1 | 2 | 1 |
Bruno Barroso | 0 | 0 | 0 | 4 | 3 | 17 | 1 | 2 | 1 |
Mattew Day | 0 | 0 | 0 | 0 | 4 | 5 | 1 | 2 | 1 |
Maxim Dokhlov | 0 | 0 | 0 | 0 | 0 | 4 | 1 | 2 | 1 |
Mehmet Bagbekleyen | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 | 1 |
Alex Yixnitsky | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
Stuart Gardner | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
Derrick May | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
共通の友人の数は以下のURLで取得できます
https://www.facebook.com/<person A>
- SocailGraph: 統括コントローラ
- Coordinate: 座標を保持する
- EquilateralPolygon: 等辺三角形を描写する
- nodeNames: ノード(Facebookの例だと)の名前
- FamiliarityAsLine: 親しさを線(の太さ)で表します
nodeNamesとFamiliarityAsLineはEquilateralPolygonに依存していますが、それぞれ独立した画像として再利用可能です。
Labels:
Processing
3 Jan 2014
Processingで正多角形を描く

最近Processingで久々に遊んでいます。以前はライブラリを組み合わせて”わーできたー”位のことをやっておりましたが、最近基礎力を身につけたいと思って幾何学的なことも軽く学んでいます。
さて、文化というかよく見かけるProcessingのサンプルは縦に長ーくて変数の値がなんだったのか途中から忘れてしまうようなものが多いです。
例えば折れ線グラフを書くようなケースの場合、
統計情報をデータソースから取得する > X軸、Y軸を描く > X、Yの最大値を求めてスケールを決める > ちょうどいい単位に目盛りを引く > 折れ線グラフを描く
といったステップを分けて踏むとコードがすっきりするのですが、一方でX軸、Y軸のディスプレイ上の座標や、XやYの最大値やなどの変数はスケッチを通して保持したいもので、そのことが縦に長ーいプログラムだとコードを複雑にしがちです。Proceccingの世界だとおそらく一回使い切りのプロジェクトもしくは次回使うことがあってもコピペして修正すれば割とすんでしまうようなことが多いのかもしれません。
でも、やっぱり再利用可能な部品を作りたいですね。コードを関数に区切ると変数のスコープが狭まるのでミニマムなことにフォーカスできます。
下記のコードでは最初に以下の引数で図形を調整できるものという条件を固持するものとして書きました。
- 正N角形のN
- 半径
# どこから読めばいいのか?
まずはsetupとdrawを読んでください。特にdrawを読むことで大まかな流れがつかめるでしょう。
- // 半径200pxの6角形を描く
- polygon = new EquilateralPolygon(6, 200);
- // 頂点を取得
- polygon.vertexes();
- // 頂点を描く
- polygon.drawVertexes();
- // 番号を描く
- polygon.drawIndexes();
// 半径200pxの6角形を描く polygon = new EquilateralPolygon(6, 200); // 頂点を取得 polygon.vertexes(); // 頂点を描く polygon.drawVertexes(); // 番号を描く polygon.drawIndexes();
肝になるところだけ軽く触れておきましょう。
# #xVertex, #vertexes()
vertexesは頂点の座標を集めています。
- public float xVertex(int i, int extra) {
- return cos(angles[i]) * (radius + extra);
- }
- public Coordinate[] vertexes() {
- vertexes = new Coordinate[numVertexes];
- float angleForSingleArc = 2 * PI / numVertexes;
- for(int i = 0; i < numVertexes; i++) {
- // Sum up angles
- angles[i] = HALF_PI - angleForSingleArc * i;
- float x = xVertex(i, 0);
- float y = - yVertex(i, 0);
- vertexes[i] = new Coordinate(x, y);
- }
- return vertexes;
- }
public float xVertex(int i, int extra) { return cos(angles[i]) * (radius + extra); } public Coordinate[] vertexes() { vertexes = new Coordinate[numVertexes]; float angleForSingleArc = 2 * PI / numVertexes; for(int i = 0; i < numVertexes; i++) { // Sum up angles angles[i] = HALF_PI - angleForSingleArc * i; float x = xVertex(i, 0); float y = - yVertex(i, 0); vertexes[i] = new Coordinate(x, y); } return vertexes; }
ある頂点のX座標は三角関数を使って以下のように求められます。sinとcosを使った辺の長さの求め方
# xVertex
cos(角度) * 半径
y座標はsinを使って以下のように求められます
# yVertex
sin(角度) * 半径
xVertex中では半径だけを使ってって計算しています。これは円の中心を起点の0,0としてその差分を座標として計算し描写しているからです。ただ描写する正確な座標ははプログラマが意識しないですむようにtranslate関数であらかじめずらしています。
# 拡張点の座標の求め方
angles[i] = HALF_PI - angleForSingleArc * i;
PIはπを意味するあらかじめProcessingに用意された定数です。上記のx, yの関数で角度が0の場合processingは右端になります。そこから始まって反時計回りに角度を表します。
0 => QUARTER_PI(45度) => HALF_PI(90度) => PI(180度) => 2 * PI(360度)
.png)
一つずつの頂点について図の一番上を起点としてそこからの角度をi毎に取得します。時計回りに点を配置することにしたので マイナスを付けて逆方向に回転させます。
angles[i] = HALF_PI - angleForSingleArc * i;
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Size of display | |
int DISPLAY_WIDTH = 500; | |
int DISPLAY_HEIGHT = 500; | |
// Colours | |
int BLACK = 0; | |
int WHITE = 255; | |
int CENTER_X = 0; | |
int CENTER_Y = 0; | |
EquilateralPolygon polygon; | |
// This class saves coordinate | |
class Coordinate { | |
int x; | |
int y; | |
// The Constructor is defined with arguments. | |
Coordinate(float _x, float _y) { | |
x = int(_x); | |
y = int(_y); | |
} | |
public String toString() { | |
return x + ", " + y; | |
} | |
} | |
class EquilateralPolygon { | |
int numVertexes; | |
int radius; | |
float[] angles; | |
Coordinate[] vertexes; | |
EquilateralPolygon(int _numVertexes, int _radius) { | |
numVertexes = _numVertexes; | |
angles = new float[numVertexes]; | |
radius = _radius; | |
} | |
public float xVertex(int i, int extra) { | |
return cos(angles[i]) * (radius + extra); | |
} | |
public float yVertex(int i, int extra) { | |
return sin(angles[i]) * (radius + extra); | |
} | |
public Coordinate[] vertexes() { | |
vertexes = new Coordinate[numVertexes]; | |
float angleForSingleArc = 2 * PI / numVertexes; | |
for(int i = 0; i < numVertexes; i++) { | |
// Sum up angles | |
angles[i] = HALF_PI - angleForSingleArc * i; | |
float x = xVertex(i, 0); | |
float y = - yVertex(i, 0); | |
vertexes[i] = new Coordinate(x, y); | |
} | |
return vertexes; | |
} | |
// Put tiny circles onto each vertex | |
public void drawVertexes() { | |
smooth(); | |
textAlign(CENTER); | |
int r = 20; | |
for (int i = 0; i < numVertexes; i++) { | |
// Draw black circle for the first vertex and draw gray ones for the rest | |
int colorValue = i == 0 ? 0 : 200; | |
stroke(colorValue); | |
strokeWeight(1); | |
ellipse(vertexes[i].x, vertexes[i].y, r, r); | |
} | |
} | |
public void drawIndexes() { | |
for (int i = 0; i < numVertexes; i++) { | |
fill(BLACK); | |
text(i, vertexes[i].x, vertexes[i].y); | |
noFill(); | |
} | |
} | |
} | |
void setup() { | |
size(DISPLAY_WIDTH, DISPLAY_HEIGHT); | |
background(WHITE); | |
noLoop(); | |
} | |
void draw() { | |
// Displace the drawn polygon by half width and half height of `display`, which means center. | |
// With `translate`, we can calculate coodinate of each vertex as distance from the center of a | |
// diagram, where it's coordinate is as (0, 0), and then, displace the polygon to actual coordinate | |
// of `display`. | |
translate(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2); | |
polygon = new EquilateralPolygon(6, 200); | |
polygon.vertexes(); | |
polygon.drawVertexes(); | |
polygon.drawIndexes(); | |
saveFrame("result.jpg"); | |
} |
# なぜOOPを使ってシンプルに書くのが難しいのか?
なぜProcessingではOOPをつかってシンプルに書くのがむずかしいのでしょうか?コードを書いていて思ったことがあります。
- 文脈依存の関数が多い
例えばこのコードに出てくる以下のようなコードは文脈依存です
fill(BLACK);
text(i, vertexes[i].x, vertexes[i].y);
noFill();
noLoop()
translate(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2)
全て後述の処理に影響します。
- 一個のループに何でもまとめてしまう
計算量が気になってしまい一個のループに何でもまとめてしまうのでそのループを含む関数がいろんなことをやってしまいがちです。lambdaを使ってループのある関数内で座標計算や描写などを遅延評価させてあげればいいんですかね。
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html?cid=7180&ssid=3993398328344#section2
以上、久々のJava勉強になりました。
Labels:
Processing
2 Jan 2014
データビジュアライゼイションに役立つかもしれないデータソースまとめ
データビジュアライゼイションに役立つかもしれないデータソースをまとめてみます。まだ少ないですが後からもちょいちょい足していきます。(*.最終更新日 2014.1.2)まとまってないだろっていう突っ込みは来年までお待ちください。
# estat, 省庁別
人口や産業、経済、エネルギーに関する政府の統計情報。国勢調査などのデータがある。
# Data Meti
経済産業省がオープンデータを実践するために設置した試験サイト。予算、決算、技術、食品、地理、防災などの情報がある。
<世界>
# IMF
緯度、経度はよく度、分、秒で表されます。度より小さい値は60進数で表されます。分は60で割り、秒は3600で割りその値を足すことで小数点以下の値に変換することが出来ます。
- QGIS
QGISは地図上に必要なレイヤー(georeferrence?)を配置するOSSです。商業利用は有料です。
# 統計データ
<日本># estat, 省庁別
人口や産業、経済、エネルギーに関する政府の統計情報。国勢調査などのデータがある。
# Data Meti
経済産業省がオープンデータを実践するために設置した試験サイト。予算、決算、技術、食品、地理、防災などの情報がある。
<世界>
# IMF
# 地図
地図のマッピングをするときはデータソースの緯度、経度を正確に地図に配置しなければなりません。そのためには背景にある地図のイメージの緯度、経度が正確でないとなりませんね。
- openStreetMap
開かれたライセンスのopenStreetMapでは緯度経度の範囲によって地図を切り取ることが出来ます。
- openStreetMap
開かれたライセンスのopenStreetMapでは緯度経度の範囲によって地図を切り取ることが出来ます。
緯度、経度はよく度、分、秒で表されます。度より小さい値は60進数で表されます。分は60で割り、秒は3600で割りその値を足すことで小数点以下の値に変換することが出来ます。
- QGIS
QGISは地図上に必要なレイヤー(georeferrence?)を配置するOSSです。商業利用は有料です。
Labels:
Processing
1 Jan 2014
has_and_belongs_to_many associationで counter_cacheを有効にする
has_and_belongs_to_many associationで counter_cacheを有効にするサンプル。
以下は Category has_and_belongs_to_many Website な関係が成立しています。HABTMでcounter_cacheを実施するには has_and_belongs_to_manyに:after_add, :after_removeなどのオプションを渡してフックするメソッドをしてあげれば良いです。
そうすることで website.categries = [category_foo, category_bar] としたときに以前のcategoriesとの差分についてcounter_cacheをincrement/decrementしてくれる。
Website自身のフック :after_save, :after_create, :after_destroyは関連を更新したときにはトリガーされないです。
*. #make!はmachinistからデータを作成しています
以下は Category has_and_belongs_to_many Website な関係が成立しています。HABTMでcounter_cacheを実施するには has_and_belongs_to_manyに:after_add, :after_removeなどのオプションを渡してフックするメソッドをしてあげれば良いです。
そうすることで website.categries = [category_foo, category_bar] としたときに以前のcategoriesとの差分についてcounter_cacheをincrement/decrementしてくれる。
Website自身のフック :after_save, :after_create, :after_destroyは関連を更新したときにはトリガーされないです。
*. #make!はmachinistからデータを作成しています
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
> Rails.env | |
# => "test" | |
> c1,c2,c3 = Category.make!,Category.make!,Category.make! | |
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message. | |
(0.4ms) BEGIN | |
test | |
SQL (0.5ms) INSERT INTO `categories` (`created_at`, `description`, `name`, `updated_at`) VALUES ('2014-01-01 04:08:14', 'syndicate strategic web-readiness', 'Books', '2014-01-01 04:08:14') | |
(1.9ms) COMMIT | |
Category Load (0.3ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = 16 LIMIT 1 | |
(0.1ms) BEGIN | |
test | |
SQL (0.2ms) INSERT INTO `categories` (`created_at`, `description`, `name`, `updated_at`) VALUES ('2014-01-01 04:08:14', 'enhance clicks-and-mortar bandwidth', 'Electronics & Computers', '2014-01-01 04:08:14') | |
(0.8ms) COMMIT | |
Category Load (0.2ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = 17 LIMIT 1 | |
(0.1ms) BEGIN | |
test | |
SQL (0.1ms) INSERT INTO `categories` (`created_at`, `description`, `name`, `updated_at`) VALUES ('2014-01-01 04:08:14', 'leverage front-end synergies', 'Home, Garden & Tools', '2014-01-01 04:08:14') | |
(0.3ms) COMMIT | |
Category Load (0.1ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = 18 LIMIT 1 | |
> website = Website.make!(categories: [c1, c2, c3]) | |
SQL (0.7ms) UPDATE `categories` SET `websites_count` = COALESCE(`websites_count`, 0) + 1 WHERE `categories`.`id` = 16 | |
SQL (0.4ms) UPDATE `categories` SET `websites_count` = COALESCE(`websites_count`, 0) + 1 WHERE `categories`.`id` = 17 | |
SQL (0.5ms) UPDATE `categories` SET `websites_count` = COALESCE(`websites_count`, 0) + 1 WHERE `categories`.`id` = 18 | |
(0.2ms) BEGIN | |
SQL (0.3ms) INSERT INTO `websites` (`created_at`, `description`, `likes_count`, `name`, `updated_at`, `url`) VALUES ('2014-01-01 04:08:17', 'engage back-end metrics', 48486642, 'Elmer Pfeffer', '2014-01-01 04:08:17', 'http://runolfonprohaska.name') | |
(0.1ms) INSERT INTO `categories_websites` (`website_id`, `category_id`) VALUES (37, 16) | |
(0.1ms) INSERT INTO `categories_websites` (`website_id`, `category_id`) VALUES (37, 17) | |
(0.1ms) INSERT INTO `categories_websites` (`website_id`, `category_id`) VALUES (37, 18) | |
(0.3ms) COMMIT | |
Website Load (0.2ms) SELECT `websites`.* FROM `websites` WHERE `websites`.`id` = 37 LIMIT 1 | |
=> #<Website id: 37, name: "Elmer Pfeffer", description: "engage back-end metrics", url: "http://runolfonprohaska.name", likes_count: 48486642, comments_count: 0, created_at: "2014-01-01 04:08:17", updated_at: "2014-01-01 04:08:17", logo_file_name: nil, logo_content_type: nil, logo_file_size: nil, logo_updated_at: nil> | |
> website.categories = [c1, c2] | |
Category Load (0.4ms) SELECT `categories`.* FROM `categories` INNER JOIN `categories_websites` ON `categories`.`id` = `categories_websites`.`category_id` WHERE `categories_websites`.`website_id` = 37 | |
(0.1ms) BEGIN | |
(0.2ms) DELETE FROM `categories_websites` WHERE `categories_websites`.`website_id` = 37 AND `categories_websites`.`category_id` IN (18) | |
SQL (0.1ms) UPDATE `categories` SET `websites_count` = COALESCE(`websites_count`, 0) - 1 WHERE `categories`.`id` = 18 | |
(1.5ms) COMMIT | |
=> [#<Category id: 16, name: "Books", description: "syndicate strategic web-readiness", created_at: "2014-01-01 04:08:14", updated_at: "2014-01-01 04:08:14", websites_count: nil>, | |
#<Category id: 17, name: "Electronics & Computers", description: "enhance clicks-and-mortar bandwidth", created_at: "2014-01-01 04:08:14", updated_at: "2014-01-01 04:08:14", websites_count: nil>] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Website < ActiveRecord::Base | |
has_and_belongs_to_many :categories, after_add: :increment_categories_counter_cache, after_remove: :decrement_categories_counter_cache | |
private | |
def increment_categories_counter_cache(category) | |
Category.increment_counter(:websites_count, category.id ) | |
end | |
def decrement_categories_counter_cache(category) | |
Category.decrement_counter(:websites_count, category.id ) | |
end | |
end |
Labels:
Ruby/ Ruby on Rails
Rails 4 エラーメッセージを完全に上書きする
メッセージを上書きしたいときはオプションで presence: { message: 'Select at least one category' } の様にメッセージを渡してあげれば良いです。
だけど、それだと主語が先頭に来てしまい"Category Select at least one category"というエラーメッセージになってしまう。
全てのメッセージを上書きしたいときはlambdaを渡してあげればよい。
だけど、それだと主語が先頭に来てしまい"Category Select at least one category"というエラーメッセージになってしまう。
全てのメッセージを上書きしたいときはlambdaを渡してあげればよい。
- validates :categories, presence: { message: -> {|location, error| raise [location, error].inspect } }
- # => [:"activerecord.errors.models.website.attributes.categories.blank", {:model=>"Website", :attribute=>"Categories", :value=>#<ActiveRecord::Associations::CollectionProxy []> }]
validates :categories, presence: { message: -> {|location, error| raise [location, error].inspect } } # => [:"activerecord.errors.models.website.attributes.categories.blank", {:model=>"Website", :attribute=>"Categories", :value=>#<ActiveRecord::Associations::CollectionProxy []> }]
Labels:
Ruby/ Ruby on Rails
Subscribe to:
Posts (Atom)