プログラミングの備忘録

プログラムをつくる過程を残すもの

processingの備忘録 -Android 第2回-

こんにちは。
processingに関してのAndroid関連の回、2回目です。

前回はprocessingがAndroidでもできるよという紹介だけでした。
なので今回は、実際にプログラムをつくってみようという回です。

processingでAndoridアプリをつくる方法は、パソコン版processingの「Androidモード」とAndroidアプリの「APDE - Android Processing IDE」の2つがありますが、今回は後者の方法でやっていきます。
スマートフォンさえあればでき、コードをいじった後の挙動がすぐ確認できるという点でとても勝手がいいと思います。

やる内容としては、以前つくった3Dでの電子軌道の描画をスマートフォン仕様に変える、ということをやってみようと思います。


さっそくやっていきます。

そのまま実行すると、mouseButton()やmouseWheel()はマウスが無いと機能しないので邪魔だよとエラーを吐きますが、mouseDragged()はスワイプと対応しているようで普通に動いてくれます。でも、せっかくなのでこれもスワイプ用に変えてみます。
mouseButton()はモデルの座標を動かす処理で、mouseWheel()はズームイン・アウトの処理でした。それぞれを、指2本でスワイプする動作とピンチイン・アウトの動作と対応させてみます。


はじめに、関数について書いておきます。
パソコン版processingのmousePressed()、mouseDragged()、mouseReleased()は、Android版ではそれぞれtouchStarted()、touchMoved()、touchEnded()に対応しています。
また、TouchEventというクラスがあり、様々な関数を使うことでタップに使われた指の数や指の位置などを得ることができます。
詳しい記述方法はコードをつくる中で説明します。


スワイプ操作

まずはスワイプ操作です。

指1本で画面を押したときにその座標に応じて処理を行うという形で導入できます。今は指1本でのスワイプ操作ではモデルの回転を行うことにします。

float x0, y0, px0, py0;

void touchMoved(TouchEvent e){ //画面を押す指が動いたとき
    x0 = e.getPointer(0).x; //指[0]のx座標
    y0 = e.getPointer(0).y; //指[0]のy座標
    
    if(e.getNumPointers() == 1){ //指が1本のとき
        //モデルの回転処理
        if(x0-px0 >= 0){
            rotX = map(x0-px0, 0, width, 0, 2*PI);
        }
        if(x0-px0 <= 0){
            rotX = map(x0-px0, -width, 0, -2*PI, 0);
        }
        rotX += protX;
        protX = rotX;
            
        if(y0-py0 >= 0){
            rotY = map(y0-py0, 0, height, 0, 2*PI);
        }
        if(y0-py0 <= 0){
            rotY = map(y0-py0, -height, 0, -2*PI, 0);
        }
        rotY += protY;
        protY = rotY;
    }

    //今の指の位置をひとつ前の位置として保存
    px0 = x0;
    py0 = y0;

これでも十分動きますが、タップした瞬間の挙動がおかしくなるので…

void touchStarted(TouchEvent e){
    px0 = e.getPointer(0).x;
    py0 = e.getPointer(0).y;
}

これを追加すると解決できます。

TouchEventクラスの「e」(任意に名前は変えられます)を呼び出して、getPointer()で指の位置を、getNumPointers()で指の数を取得しています。
指は配列で管理されているようで、1本目の指は0、2本目の指は1、…というようになりますので、指1本でのスワイプで指の座標を知りたければ引数は0になります。
回転の処理は以前に説明しているので省略します。


指2本でスワイプするときは1を2に、処理をモデルの回転から座標移動に変えて追加すれば良いだけです。

if(e.getNumPointers() == 2){ //指が2本のとき
        //モデルの座標移動処理
        xc += py0-y0;
        pxc = xc;
        yc += x0-px0;
        pyc = yc;
    }

指は2本使っていますが、どちらかの座標さえわかればどれくらい動かせば良いかわかるので、指1本スワイプで追加したx0, y0, px0, py0を利用しています。
モデルの座標移動処理といっているのでモデル自体の座標に指の移動分を足しているのかと思われるかもしれませんが、このコードではカメラの位置を動かすことで相対的にモデルが動いているように見せているだけです。


ピンチイン・アウト操作

続いてピンチイン・アウト操作です。

ピンチ操作は指2本で画面を押したときに、指の距離が縮まったらピンチイン、広がったらピンチアウトです。
処理はそれぞれズームアウト、ズームインに対応させます。

float x1, y1, distance, pdistance;

if(e.getNumPointers() == 2){ //指が2本のとき
    x1 = e.getPointer(1).x; //指[1]のx座標
    y1 = e.getPointer(1).y; //指[1]のy座標
    distance = sqrt((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1)); //指[0]と指[1]の距離の計算
            
    if(distance > pdistance){ //距離が広がっていれば
        s *= 1.01; //拡大
    }     
    if(distance < pdistance){ //距離が縮まっていれば
        s *= 0.99 //縮小
    }
}

pdistance = distance;

指の距離をdistanceとして計算して、それがひとつ前のもの(pdistance)と比べて大きければ拡大、小さければ縮小するようにしています。
拡大・縮小自体はdraw()内のscale()関数で行っているので、ここではその引数の指定だけ行います。
(if(e.getNumPointers() == 2)の条件が指2本スワイプとかぶっているので、実際はまとめて書きます。)


まとめ

一個一個書かれてもどこに何を書くかよくわからないので、まとめたものを載せておきます。
スマホだと重かったので、$\theta, \phi$に2°ずつ足すように変えてあります。)

float rotX, rotY;
float protX, protY;
float xc, yc, zc;
float pxc, pyc, pzc;
float s;
float x0, y0, px0, py0, x1, y1, distance, pdistance;

float r;
float[][] x = new float[360][180];
float[][] y = new float[360][180];
float[][] z = new float[360][180];

void setup(){
    fullScreen(P3D);
    
    xc = 0;
    yc = 0;
    zc = 500;
    s = 1;
    
    r = 100;
}

void draw(){
    background(255);
    
    camera(0, 0, 1000, xc, yc, zc, 1, 0, 0);
    rotateX(rotX);
    rotateY(rotY);
    scale(s);
    
    stroke(255, 0, 0);
    line(0, 0, 0, 500, 0, 0);
    stroke(0, 255, 0);
    line(0, 0, 0, 0, 500, 0);
    stroke(0, 0, 255);
    line(0, 0, 0, 0, 0, -500);
    
    for(int theta = 0; theta < 360; theta += 2){
    for(int phi = 0; phi < 180; phi += 2){
        float t = radians(theta);
        float p = radians(phi);
        
        float Y;
        Y = sin(t)*cos(p);
     
        x[theta][phi] = abs(r*Y) * sin(t) * cos(p);
        y[theta][phi] = abs(r*Y) * sin(t) * sin(p);
        z[theta][phi] = abs(r*Y) * cos(t);
        
        if(r*Y > 0) stroke(0, 0, 255);
        if(r*Y < 0) stroke(255, 0, 0);
        point(x[theta][phi], y[theta][phi], -z[theta][phi]);
    }
    }
}

void touchStarted(TouchEvent e){
    px0 = e.getPointer(0).x;
    py0 = e.getPointer(0).y;
}

void touchMoved(TouchEvent e){
    x0 = e.getPointer(0).x;
    y0 = e.getPointer(0).y;
    
    if(e.getNumPointers() == 1){
        if(x0-px0 >= 0){
            rotX = map(x0-px0, 0, width, 0, 2*PI);
        }
        if(x0-px0 <= 0){
            rotX = map(x0-px0, -width, 0, -2*PI, 0);
        }
        rotX += protX;
        protX = rotX;
            
        if(y0-py0 >= 0){
            rotY = map(y0-py0, 0, height, 0, 2*PI);
        }
        if(y0-py0 <= 0){
            rotY = map(y0-py0, -height, 0, -2*PI, 0);
        }
        rotY += protY;
        protY = rotY;
    }
    
    if(e.getNumPointers() == 2){   
        xc += py0-y0;
        pxc = xc;
        yc += x0-px0;
        pyc = yc;


        x1 = e.getPointer(1).x;
        y1 = e.getPointer(1).y;
        distance = sqrt((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1));
            
        if(distance-pdistance > 0){
            s *= 1.01;
        }
        if(distance-pdistance < 0){
            s *= 0.99;
        }
    }
        
    px0 = x0;
    py0 = y0;
    pdistance = distance;   
}


というわけで、今回は3Dをグリグリ動かすプログラムのスマートフォンバージョンをつくってみました。
processing for Androidのホームページ(?)にあるレファレンスを見ると、VRやARもできるようなことを書いてあるのでもしかしたら今後触れるかもしれません。

切りがいいので今回はここまでにします。また次回。