Processingには、この緊急モーションセンサーから値を得る「Sudden Motion Sensorライブラリ」があります。緊急モーションセンサーでは、以前扱った「加速度センサ」のように、コンピュータ本体を傾けたり衝撃を与えたりすることによって変化する値(XYZ軸に対する3つの値)が得られます。
Sudden Motion Sensorライブラリのサイトに従えば、3つの値は以下の方法で読み込むことが可能になります。尚、ライブラリをダウンロード+インストールする必要があります。
//ライブラリを取り込む
import sms.*;
void setup() {
//画面サイズをとりあえず200角
size(200,200);
}
void draw() {
//3つの値を読み込み、配列に代入
int[] vals = Unimotion.getSMSArray();
//3つの値を出力
println(vals[0] + " " + vals[1] + " " + vals[2]);
}
上記プログラムによって出力された値は以下のようになりました(機種や状況によって多少誤差が含まれるかもしれません)。
水平時 x: 0, y: -3, z:56
左90度 x: 52, y: -3, z: 6
右90度 x:-51, y: -3, z: 5
前90度 x: 1, y: 49, z: 6
後90度 x: 1, y:-54, z: 5
xの振り幅:103(-51から52までの±51.5)
yの振り幅:103(-54から49までの±51.5)
になります。それぞれの振り幅からxとyの中点(水平時の値)を新たに求めると、
xの中点:(-51+52)/2=0.5
yの中点:(-54+49)/2=-2.5
になります。これら中点の値をオフセット値として用いることにします。つまり、計測された値からオフセット値を差し引いて角度の計算をすることになります。以前の「Arduino 加速度センサ」のときと同様に、出力値からそれぞれの角度を求める式を用意します。それぞれの角度をradX、radY、オフセット値をoffsetX、offsetY、読み取り値をx、yとすると、
//atan2()で求める場合
radX=atan2((x-offsetX),sqrt(51.5*51.5-(x-offsetX)*(x-offsetX)))
radY=atan2((y-offsetY),sqrt(51.5*51.5-(y-offsetY)*(y-offsetY)))
//またはacos()、asin()で求める場合
radX=asin((x-offsetX)/51.5)
radY=acos((y-offsetY)/51.5)
になります。式中の51.5は加速度1G(重力)の時の値です(機種によっては256くらいのときもあります)。
以上の式を使って、コンピュータ本体を傾けることでProcessing画面上の3Dモデルを動かしてみます。
3Dモデルに関しては、前回の記事「Processing 3Dモデル/OBJ Loader」のものを使うことにします。コンピュータを傾けた方向に、画面内の3Dモデルも同様に傾く内容とします。
スケッチフォルダ内にdataフォルダを作成し、3Dモデルのデータを入れておいて下さい(3Dデータは、ここからダウンロードできます/.objファイルと.mtlファイルの二つが必要です)。
(コンピュータを傾けた時の3Dモデル/前回ブログの3Dモデルを使用)
//ライブラリのインポート
import sms.*;
import saito.objloader.*;
//モデルのオブジェクトを用意
OBJModel model;
void setup() {
//3D画面サイズ設定
size(400,400,P3D);
//モデルのオブジェクトを生成
model=new OBJModel(this);
//3Dデータ読み込み
model.load("macbook.obj");
//ワイヤーフレームなし
noStroke();
}
void draw() {
//3つの値を読み込み、配列に代入
int[] vals = Unimotion.getSMSArray();
//println(vals[0] + " " + vals[1] + " " + vals[2]);
//背景描画
background(50);
//直線光の設定
directionalLight(200, 200, 200, -1, 1, -1);
//環境光の設定
ambientLight(200, 200, 200);
//3Dモデルの位置座標設定
translate(width/2,height*2/3,0);
//オフセット値
float offsetX=-2.5;
float offsetY=0.5;
//角度の計算:atan2()で求める場合
float radX=-atan2(vals[1]-offsetX,sqrt(51.5*51.5-(vals[1]-offsetX)*(vals[1]-offsetX)));
float radY=-atan2(vals[0]-offsetY,sqrt(51.5*51.5-(vals[0]-offsetY)*(vals[0]-offsetY)));
//またはacos()、asin()で求める場合
//float radX=asin((vals[1]-offsetX)/51.5);
//float radY=acos((vals[0]-offsetY)/51.5);
//回転角度
rotateX(radX+PI/2);
rotateY(radY);
//三角形分割で面を生成する
model.drawMode(TRIANGLES);
//スケール(200倍)
scale(200);
//モデル描画
model.draw();
}
画面内の3D座標は、
左:-X
右:+X
上:-Y
下:+Y
後:-Z
前:+Z
という関係になります。
緊急モーションセンサーのX軸は、Processing上のY軸に対応しているので、vals[0]の値を3DモデルのY軸回転角度へ代入し、val[1]の値は3DモデルのX軸回転角度へ代入します。表示上90度X軸に対してずれていたので、rotateX(radX+PI/2)というようにradXにPI/2(90度)足しておきました。
directionalLight()は太陽光のような直線光であり、括弧内の数値については、最初の3つがRGBで光の色を指定、最後の3つが(0,0,0)の原点を基準に光の向きを設定することになります。
ambientLight()は環境光であり、光の向きはなく、空間全体を明るくしたり暗くしたりし(あるいは色を変える)、RGBの3つの数値で指定します。
もう一つのサンプルとして、コンピュータ自体をコントローラとして傾けて、水平面上のボールを転がすプログラムをしてみます。今回は、setup(){...}内で、一度緊急モーションセンサーから値を読み込み、それらをオフセット値として使うことにします。こうすることで、コンピュータを平らな場所においてプログラムを開始したときの状態(オフセット値)を記憶させておくことができます。ellipse()で擬似的な影を地面に落とすことで立体感がでるようにします。
(コンピュータの傾きに合わせてボールが転がる)
//ライブラリのインポート
import sms.*;
//ボールの座標用変数
float xPos,yPos;
//オフセット用変数
float xOffset;
float yOffset;
void setup() {
//3D画面設定
size(400,400,P3D);
//ワイヤーフレームなし
noStroke();
//水平状態の読み込み
int[] vals = Unimotion.getSMSArray();
//読み込み値をオフセット値に設定する
xOffset=vals[0];
yOffset=vals[1];
}
void draw() {
//モーションセンサーからの読み込み
int[] vals = Unimotion.getSMSArray();
//背景色
background(220);
//画面上半分の塗色
fill(50);
//画面上半分の矩形
rect(0,0,width,height/2);
//ボールの速度の計算
float xSpeed=vals[0]-xOffset;
float ySpeed=vals[1]-yOffset;
//ボール移動量の計算
xPos+=-xSpeed;
yPos+=ySpeed;
//ボール位置の設定
translate(width/2+xPos,height*4/5,-200+yPos);
//影の塗色
fill(50);
//影の座標と大きさ
ellipse(-25,30,80,80/5);
//直線光の設定
directionalLight(255, 255, 255, -1, 1, 0);
//ボールの塗色
fill(255,100,50);
//ボール描画
sphere(30);
}
緊急モーションセンサーの読み取り値からオフセット値を差し引いた値を、そのままボールのスピードに反映させています。読み取られたY方向の値は、画面内のZ座標(前後の軸)に対応するので、yPosをtranslate()のZ軸に代入してあります。実際に動かしてみて、向きが逆であったり、座標軸がきちんと対応していない場合は、値にマイナスを掛けたり、代入先を入れ替えたりして調整してみて下さい。
ボールの位置設定のためのtranslate()は、まずX座標をwidth/2で左右中央、Y座標をheight*4/5で画面上下4/5の位置、Z座標を-200奥とした座標を基準とし、その基準の座標に変化する値となるxPosとyPosを追加して、最終的な位置を決定しています。
疑似の影として用いたellipse()は2D用の図形でありX座標とY座標しか設定できませんが、translate()以後に挿入してあるので、translate()のZ軸の値に合わせて、前後に動きつつ大きさも変化します(2D図形は、3D空間上ではZ座標値が0の位置に配置されており、translate()以後に書いた2D図形は、Z軸の値を変化させれば、見た目の大きさや位置も影響を受けて変化します)。同様に、directionalLight()に対しても、2D図形をdirectionalLight()以前に書けば、光の影響を受けませんが、directionalLight()以後に2D図形を書くと、光の影響を受けて図形自体に陰影がつきます。そのため、directionalLight()は、プログラムの冒頭の方に書かずに、ボール描画の直前(2D図形描画以降)に書いておきます。
5 件のコメント:
はじめまして、
Gainer互換のGinger/Pepper/Sugarを開発している”もあ”と申します。
エレキジャックブログの連載で本プログラムを取り上げてさせて頂きたいと考えていますが、問題ありませんでしょうか?
事例として、Gainer互換のPepperで制御する加速度センサから本プログラムを一部修正して利用したいと考えています。また、引用先として「建築発明工作ゼミ2008」を参照させて頂きます。
もあさんへ。
はい、大丈夫です。どうぞお使いください(こちらこそ、喜んで)。
kousakuさん
早速ありがとうございます。
今週中に掲載になると思いますが、掲載されましたら、またお知らせします。
エレキジャックブログの記事がサイトに掲載されましたので、報告します。
http://www.eleki-jack.com/FC/2008/12/gingerpeppersugar9.html
ここのProcessingから3Dを使うプログラムを使わせて頂きました。ありがとうございます。
また、何かありましたら連絡頂けると幸いです。今後ともよろしくお願いします。
こちらこそ参考になる記事を読ませてい頂きました。何か面白いことがあれば連絡させて頂きます。今後ともよろしくお願い致します。
コメントを投稿