@blog.justoneplanet.info

日々勉強

Push Notificationを使ってみる

■プロビジョニングポータル

App IDs > Configure > Enable for Apple Push Notification serviceにチェックを入れ、「Development Push SSL Certificate」のConfigureをクリックする。

キーチェーンアクセスを起動させ、キーチェーンアクセス > 証明書アシスタント > 認証局に証明書を要求

以下の項目を入力する。

  1. ユーザーのメールアドレス
  2. 通称(コモンネーム)

ディスクに保存を選択するとCertificateSigningRequest.certSigningRequestというファイルが保存される。「続ける」を押す。

continueを押し保存したファイルをアップロードすると、「Development Push SSL Certificate」のStatusがEnableになるので、Downloadしてダブルクリックしキーチェーンアクセスに保存する。

キーチェーンアクセス > 自分の証明書

  1. Apple Development Push Servicesの証明書を選択し、ファイルから書き出しを実行してcert.p12で保存
  2. Apple Development Push Servicesの秘密鍵を選択し、ファイルから書き出しを実行してkey.p12で保存

保存したディレクトリで以下のコマンドを実行する。

openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12
openssl pkcs12 -nocerts -out key.pem -in key.p12
openssl rsa -in key.pem -out key-noenc.pem
cat cert.pem key-noenc.pem > dev.pem

ここで生成されたdev.pemはサーバー側で使用する。

■クライアント側の実装

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | 
                                                                           UIRemoteNotificationTypeBadge |
                                                                           UIRemoteNotificationTypeSound)];
    return YES;
}

以下のメソッドはdevice tokenを受け取った時に実行される。binaryを変換して(文字列を取り出して)サーバー側の仕様にそった形式にして送信する。

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *str = [NSString stringWithFormat:@"%@",deviceToken];
    NSString *newString = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    newString = [newString stringByReplacingOccurrencesOfString:@"<" withString:@""];
    newString = [newString stringByReplacingOccurrencesOfString:@">" withString:@""];
    
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setObject:newString forKey:KEY_DEVICE_TOKEN];
    [ud synchronize];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"error:%@", error);
}

リモート通知はシミュレータではサポートされていない。また、「エンタイトルメント文字列が見つかりません」と表示されたら、有効な開発用のプロビジョニングプロファイルがインストールできていないという事である。

以下のメソッドはpush notificationを受け取った時に実行される。

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    [notification setFireDate:[NSDate date]];
    [notification setTimeZone:[NSTimeZone defaultTimeZone]];
    [notification setAlertBody:[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]];
    [notification setUserInfo:userInfo];
    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}

■サーバー側の実装

Socket通信ができれば言語は特に限定しないが今回はゆとり言語PHPとゆとりライブラリでやる。

cd vendors/
wget http://apns-php.googlecode.com/files/ApnsPHP-r100.zip
unzip ApnsPHP-r100.zip

Cakeとの親和性が悪いので以下のようにして読み込む。

App::import('Vendor', 'ApnsPHP/Log/Interface');
App::import('Vendor', 'ApnsPHP/Log/Embedded');
App::import('Vendor', 'ApnsPHP/Abstract');
App::import('Vendor', 'ApnsPHP/Exception');
App::import('Vendor', 'ApnsPHP/Push');
App::import('Vendor', 'ApnsPHP/Message');

$push = new ApnsPHP_Push(
    ApnsPHP_Abstract::ENVIRONMENT_SANDBOX,
    '/path-to-cert/dev.pem'
);
$push->connect();
$message = new ApnsPHP_Message('123456789db0b2478f123456789f8067e3c61234567891df1d04851234567898');//device token
$message->setText('こんにちはこんばんわ');
$message->setExpiry(30);
$push->add($message);
$push->send();
$push->disconnect();

参考

AndroidのTextViewのフォントをassetディレクトリに配置したものにする

とりあえずmac本体のフォントでテストする。

cp /Library/Fonts/Osaka.ttf ~/Dev/project/app/assets/

以下のように記述する。

((TextView) view.findViewById(R.id.textview)).setTypeface(Typeface.createFromAsset(context.getAssets(), "Osaka.ttf"));

リリースする場合はフォントの著作権に気をつけて適切なフォントを選択する。

XCodeでC言語を使って遊ぶ

Mac OS X ApplicationからCommand Line Toolを選択するととっつきやすい。

#include <stdio.h>

int main (int argc, const char * argv[])
{
    printf("こんにちは!\n%d円よこしてください", );
    return 0;
}
#include <stdio.h>

int main (int argc, const char * argv[])
{
    int cost = 100;
    printf("こんにちは!%d円よこしてください\n", cost);
    cost = 10000;
    printf("こんにちは!%d円よこしてください\n", cost);
    return 0;
}

■インクルードガード

main.c

#include <stdio.h>
#include "hoge.h"

int main (int argc, const char * argv[])
{
    printf("こんにちは!%d円よこしてください\n", hoge());
}

hoge.h

#ifndef INCLUDED_MAIN
#define INCLUDED_MAIN

int hoge();

#endif

hoge.c

#include "hoge.h"

int hoge()
{
    return 100;
}

android版かおもじ辞典をリリースしました

Simejiの拡張機能としてandroid版かおもじ辞典をリリースしましたʕ→ᴥ←ʔ キュンキュン!

■ダウンロード

こちらより無料でダウンロードできます。

■機能

マイ顔文字

登録してシェアすることができます。

みんなの顔文字

みんなが登録した顔文字を見ることができます。好きなモノをロングタップしてマイ顔文字に保存できます。

人気の顔文字

みんなが使ってる顔文字の人気ランキングです。知らない顔文字があったらロングタップして保存しましょう。

■Chrome版

こちらをご覧ください。

■iPhone版

こちらをご覧ください。

AndroidでSQLiteを使ってテーブル構造を変える

class HogeHelper extends SQLiteOpenHelper {
    private Context mContext;
    public HogeHelper(Context context, CursorFactory factory) {
        super(context, CategoryTable.FILENAME, factory, CategoryTable.VERSION);
        mContext = context;
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE `tbl`(`_id` INTEGER PRIMARY KEY AUTOINCREMENT,`name` TEXT);");
    }
    
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion == 1 && newVersion == 2) {
            // ordカラムの追加
            db.execSQL("ALTER TABLE `tbl` ADD `ord` INTEGER AFTER `name`;");
            
            // 既存データの取得(idだけ)
            Cursor cursor = db.query(
                    "tbl",
                    new String[]{
                            "_id"
                    },
                    null,
                    null,
                    null,
                    null,
                    "_id ASC"
            );
            
            // 既存データのordカラムのデータを入れる
            int ord = 0;
            if(cursor.moveToFirst()){
                do{
                    db.execSQL(
                        "UPDATE `tbl` SET `ord` = ? WHERE `_id` = ?",
                        new String[]{String.valueOf(ord),
                        String.valueOf(cursor.getLong(cursor.getColumnIndex("_id")))}
                    );
                    ord = ord + 10;
                }
                while(cursor.moveToNext());
            }
            cursor.close();
        }
    }
}

onUpgradeメソッド内で以下のようにデータの取得はできる。

Cursor cursor = db.query(
    "tbl",
    new String[]{"_id"},
    null,
    null,
    null,
    null,
    "_id ASC"
);

onUpgradeメソッド内で以下のようにデータの変更はできない。

db.update(...);

dbはread専用であると怒られる。以下のように書込専用のdbを取得することもできない。

getWritableDatabase();

getWritableDatabaseメソッドがhelperのonUpgradeをcallするためである。

OpenGL ESメモ

コードを書いておく。ただそれだけ。

package info.justoneplanet.android.glsample;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;

public class MainActivity extends Activity {
    private GLSurfaceView gLSurfaceView;



    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        gLSurfaceView = new GLSurfaceView(this);
        gLSurfaceView.setRenderer(new Renderer());
        setContentView(gLSurfaceView);
    }
    
    @Override
    public void onPause()
    {
        super.onPause();
        gLSurfaceView.onPause();
    }
    
    @Override
    public void onResume()
    {
        super.onResume();
        gLSurfaceView.onResume();
    }
    
    private class Renderer implements GLSurfaceView.Renderer
    {
        @Override
        public void onDrawFrame(GL10 gl) {
            gl.glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
            
            // camera
            {
                gl.glMatrixMode(GL10.GL_PROJECTION);
                gl.glLoadIdentity();
                
                GLU.gluPerspective(gl, 45.0f, 1.0f, 0.01f, 100.0f);
                GLU.gluLookAt(gl, 0, 0, 10.0f, 0, 0, 0.0f, 0.0f, 1.0f, 0.0f);
            }
            
            // vertices
            {
                final float one = 1.0f;
                
                final float[] vertices = new float[]{
                         one,   one,   one,
                         one,   one,  -one,
                        -one,   one,   one,
                        -one,   one,  -one,
                         one,  -one,   one,
                         one,  -one,  -one,
                        -one,  -one,   one,
                        -one,  -one,  -one,
                };
                
                FloatBuffer fb = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
                
                fb.put(vertices);
                fb.position(0);
                
                gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
                gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
            }
            
            {
                gl.glMatrixMode(GL10.GL_MODELVIEW);
                gl.glRotatef(1.0f, 0, 1, 0);// 回転
            }
            
            {
                final byte[] indices = new byte[]{0, 1, 2, 3, 6, 7, 4, 5, 0, 1};
                ByteBuffer bb = ByteBuffer.allocateDirect(indices.length).order(ByteOrder.nativeOrder());
                bb.put(indices);
                bb.position(0);
                gl.glColor4f(0, 1, 0, 1);
                gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, bb.capacity(), GL10.GL_UNSIGNED_BYTE, bb);
            }
            
            {
                final byte[] indices = new byte[]{1, 5, 3, 7};
                ByteBuffer bb = ByteBuffer.allocateDirect(indices.length).order(ByteOrder.nativeOrder());
                bb.put(indices);
                bb.position(0);
                gl.glColor4f(0, 0, 1, 1);
                gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, bb.capacity(), GL10.GL_UNSIGNED_BYTE, bb);
            }
            
            {
                final byte[] indices = new byte[]{0, 2, 4, 6};
                ByteBuffer bb = ByteBuffer.allocateDirect(indices.length).order(ByteOrder.nativeOrder());
                bb.put(indices);
                bb.position(0);
                gl.glColor4f(1, 0, 0, 1);
                gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, bb.capacity(), GL10.GL_UNSIGNED_BYTE, bb);
            }
         }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            gl.glViewport(0, 0, width, height);
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
       }
    }
}
package info.justoneplanet.android.glsample;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;

public class MainActivity extends Activity {
    private GLSurfaceView gLSurfaceView;



    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        gLSurfaceView = new GLSurfaceView(this);
        gLSurfaceView.setRenderer(new Renderer());
        setContentView(gLSurfaceView);
    }
    
    @Override
    public void onPause()
    {
        super.onPause();
        gLSurfaceView.onPause();
    }
    
    @Override
    public void onResume()
    {
        super.onResume();
        gLSurfaceView.onResume();
    }
    
    private class Renderer implements GLSurfaceView.Renderer
    {
        @Override
        public void onDrawFrame(GL10 gl) {
            gl.glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
            
            // camera
            {
                gl.glMatrixMode(GL10.GL_PROJECTION);
                gl.glLoadIdentity();
                
                GLU.gluPerspective(gl, 45.0f, 1.0f, 0.01f, 100.0f);
                GLU.gluLookAt(gl, 0, 5.0f, 5.0f, 0, 0, 0.0f, 0.0f, 1.0f, 0.0f);
            }
            
            // vertices
            {
                final float one = 1.0f;
                
                final float[] vertices = new float[]{
                         one,   one,   one,
                         one,   one,  -one,
                        -one,   one,   one,
                        -one,   one,  -one,
                         one,  -one,   one,
                         one,  -one,  -one,
                        -one,  -one,   one,
                        -one,  -one,  -one,
                };
                
                FloatBuffer fb = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
                
                fb.put(vertices);
                fb.position(0);
                
                gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
                gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
            }
            
            {
                gl.glMatrixMode(GL10.GL_MODELVIEW);
                gl.glRotatef(1.0f, 0, 1, 0);// 回転
            }
            
            {
                final byte[] indices = new byte[]{
                        0, 1, 2,
                        2, 1, 3,
                        2, 3, 6,
                        6, 3, 7,
                        6, 7, 4,
                        4, 7, 5,
                        4, 5, 0,
                        0, 5, 1,
                        1, 5, 3,
                        3, 5, 7,
                        0, 2, 4,
                        4, 2, 6,
                };
                ByteBuffer bb = ByteBuffer.allocateDirect(indices.length).order(ByteOrder.nativeOrder());
                bb.put(indices);
                bb.position(0);
                gl.glColor4f(1, 1, 0, 1);
                gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, bb.capacity(), GL10.GL_UNSIGNED_BYTE, bb);
            }
         }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            gl.glViewport(0, 0, width, height);
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
       }
    }
}
package info.justoneplanet.android.glsample;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;

public class MainActivity extends Activity {
    private GLSurfaceView gLSurfaceView;



    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        gLSurfaceView = new GLSurfaceView(this);
        gLSurfaceView.setRenderer(new Renderer());
        setContentView(gLSurfaceView);
    }
    
    @Override
    public void onPause()
    {
        super.onPause();
        gLSurfaceView.onPause();
    }
    
    @Override
    public void onResume()
    {
        super.onResume();
        gLSurfaceView.onResume();
    }
    
    private class Renderer implements GLSurfaceView.Renderer
    {
        private float aspect = 0.0f;
        private int vertices = 0;
        private int indices  = 0;
        private int indicesLength;

        @Override
        public void onDrawFrame(GL10 gl) {
            gl.glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
            
            // camera
            {
                gl.glMatrixMode(GL10.GL_PROJECTION);
                gl.glLoadIdentity();
                
                GLU.gluPerspective(gl, 45.0f, 1.0f, 0.01f, 100.0f);
                GLU.gluLookAt(gl, 0, 5.0f, 5.0f, 0, 0, 0.0f, 0.0f, 1.0f, 0.0f);
            }
            
            {
                gl.glMatrixMode(GL10.GL_MODELVIEW);
                gl.glRotatef(1.0f, 0, 1, 0);
            }
            
            GL11 gl11 = (GL11) gl;
            gl11.glDrawElements(GL10.GL_TRIANGLES, indicesLength, GL10.GL_UNSIGNED_BYTE, 0);
            
         }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            aspect = (float) width / (float) height;
            gl.glViewport(0, 0, width, height);
            
            GL11 gl11 = (GL11) gl;
            {
                int[] buffer = new int[2];
                gl11.glGenBuffers(2, buffer, 0);
                
                vertices = buffer[0];
                indices = buffer[1];
            }
            
            // vertices
            {
                final float one = 1.0f;
                
                final float[] vertices = new float[]{
                         one,   one,   one,
                         one,   one,  -one,
                        -one,   one,   one,
                        -one,   one,  -one,
                         one,  -one,   one,
                         one,  -one,  -one,
                        -one,  -one,   one,
                        -one,  -one,  -one,
                };
                
                FloatBuffer fb = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
                
                fb.put(vertices);
                fb.position(0);
                
                gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
                gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, this.vertices);
                gl11.glBufferData(GL11.GL_ARRAY_BUFFER, fb.capacity() * 4, fb, GL11.GL_STATIC_DRAW);
                
                gl11.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
            }
            
            {
                final byte[] indices = new byte[]{
                        0, 1, 2,
                        2, 1, 3,
                        2, 3, 6,
                        6, 3, 7,
                        6, 7, 4,
                        4, 7, 5,
                        4, 5, 0,
                        0, 5, 1,
                        1, 5, 3,
                        3, 5, 7,
                        0, 2, 4,
                        4, 2, 6,
                };
                ByteBuffer bb = ByteBuffer.allocateDirect(indices.length).order(ByteOrder.nativeOrder());
                bb.put(indices);
                indicesLength = bb.capacity();
                bb.position(0);
                
                gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, this.indices);
                gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, bb.capacity(), bb, GL11.GL_STATIC_DRAW);
            }
       }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        }
    }
}

androidのOpenGL ESでカメラを使う

こんな感じ。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    gl.glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    
    {
        final float[] vertices = new float[]{
                0.0f,  1.0f,  1.0f,
                0.0f, -1.0f,  0.0f,
                1.0f, -1.0f, -1.0f,
        };
        
        FloatBuffer fb = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        
        fb.put(vertices);
        fb.position(0);
        
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
    }
    
    // カメラ
    {
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        
        GLU.gluPerspective(gl, 45.0f, 1.0f, 0.01f, 100.0f);// 視野の設定
        GLU.gluLookAt(gl, 0, 0, 10.0f, 0, 0, 0.0f, 0.0f, 1.0f, 0.0f);// カメラの位置向き
    }
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3);
}

インデックスバッファを使用すると以下のようになる。

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    gl.glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    
    {
        final float[] vertices = new float[]{
                0.0f,  1.0f,  1.0f,
                0.0f, -1.0f,  0.0f,
                1.0f, -1.0f, -1.0f,
        };
        
        FloatBuffer fb = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        
        fb.put(vertices);
        fb.position(0);
        
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
    }
    
    // カメラ
    {
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        
        GLU.gluPerspective(gl, 45.0f, 1.0f, 0.01f, 100.0f);// 視野の設定
        GLU.gluLookAt(gl, 0, 0, 10.0f, 0, 0, 0.0f, 0.0f, 1.0f, 0.0f);// カメラの位置向き
    }
    
    // インデックスバッファ
    {
        final byte[] indices = new byte[]{0, 1, 2};
        ByteBuffer bb = ByteBuffer.allocateDirect(indices.length).order(ByteOrder.nativeOrder());
        bb.put(indices);
        bb.position(0);
        
        gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 3, GL10.GL_UNSIGNED_BYTE, bb);
    }
}

JavaScriptでプリム法

重み付き無向グラフの最小全域木を求めるアルゴリズム。任意の辺から始めて全頂点を覆うまで連続的に木の大きさを拡大し、全ての辺の重みの総和が最小となる経路を求める。

■実装

以下のように実装した。

/**
 * Vertex
 * @param int value
 */
var Vertex = function(value){
    var self = this;
    self.value    = value;
    self.neighbor = [];
    self.appendNeighbor = function(){
        for(var i = 0; i < arguments.length; i++){
            self.neighbor.push(arguments[i]);
        }
    }
}

// set up a graph
var v0 = new Vertex(0);
var v1 = new Vertex(1);
var v2 = new Vertex(2);
var v3 = new Vertex(3);
var v4 = new Vertex(4);
v0.appendNeighbor(
    {"vertex" : v1, "weight" : 2},
    {"vertex" : v3, "weight" : 8},
    {"vertex" : v4, "weight" : 4}
);
v1.appendNeighbor(
    {"vertex" : v0, "weight" : 2},
    {"vertex" : v2, "weight" : 3}
);
v2.appendNeighbor(
    {"vertex" : v1, "weight" : 3},
    {"vertex" : v4, "weight" : 1},
    {"vertex" : v3, "weight" : 5}
);
v3.appendNeighbor(
    {"vertex" : v2, "weight" : 5},
    {"vertex" : v4, "weight" : 7},
    {"vertex" : v0, "weight" : 8}
);
v4.appendNeighbor(
    {"vertex" : v2, "weight" : 1},
    {"vertex" : v3, "weight" : 7},
    {"vertex" : v0, "weight" : 4}
);
var V = [v0, v1, v2, v3, v4];

// initialize
var key  = [];
var pred = [];
for(var i = 0; i < V.length; i++){
    key[V[i]['value']]  = Number.POSITIVE_INFINITY;
    pred[V[i]['value']] = -1;
}
key[0] = 0;// 任意の1頂点をスタート地点とするため優先度を0にする
var bh = new BinaryHeap();
for(var i = 0; i < V.length; i++){
    // 頂点を優先度付きキューに格納
    bh.insert(V[i], key[V[i]['value']]);
}

// calc
while(bh.getList().length > 0){
    var u = bh.getPrior();// 優先度で先頭にくる頂点を取り出す
    for(var i = 0; i < u['neighbor'].length; i++){// 経路が存在している頂点でループ
        var neighbor = u['neighbor'][i]['vertex'];
        // 頂点(a)がキューに存在している場合
        if(bh.inQueue(neighbor)){
            var weight = u['neighbor'][i]['weight'];
            // かつ頂点(a)まで距離が前回のループまでに到達した距離よりも短い場合
            if(weight < key[neighbor['value']]){
                pred[neighbor['value']] = u['value'];// 頂点を配列に格納
                key[neighbor['value']]  = weight;// 距離を配列に格納
                bh.changePriority(neighbor, weight);// 優先度を更新(キューの先頭に近づく)
            }
        }
    }
}

// result
console.log("key:");
console.log(key);
console.log("pred:");
console.log(pred);

結果

最小全域木の距離の総和は以下を足した値となる。

0 2 3 5 1

計算時に辿った経路は以下のようになる。

-1 0 1 2 2

優先度付きキュー

以前の記事で使用したコードにメソッドを追記して使用した。

/**
 * BinaryHeap
 */
var BinaryHeap = function(){
    var self = this;
    self._ary  = [];
}
BinaryHeap.prototype._build = function(){
    var self = this;
    /**
     * heapify
     * 3要素を比較し最も小さい要素を親とする
     * @param {array} ary
     * @param {int} i
     * @param {max} max
     */
    var heapify = function(ary, i, max){
        /**
         * swap
         * @param {array} ary
         * @param {int} x
         * @param {int} y
         */
        var swap = function(ary, x, y){
            var a = ary[x];
            var b = ary[y];
            ary[x] = b;
            ary[y] = a;
            return true;
        }

        var l = 2 * i + 1;
        var r = 2 * i + 2;
        var li = 0;
        if(l < max && ary[l].priority < ary[i].priority){
            li = l;
        }
        else{
            li = i;
        }
        if(r < max && ary[r].priority < ary[li].priority){
            li = r;
        }
        if(li !== i){
            swap(ary, i, li);
            heapify(ary, li, max);
        }
    }
    var ary = self._ary;
    for(var i = ary.length - 1; i >= 0; i--){
        heapify(ary, i, self._ary.length);
    }
}
/**
 * BinaryHeap::insert
 * 要素をヒープに追加する
 * @param {Object} elm
 * @param {int} priority
 */
BinaryHeap.prototype.insert = function(elm, priority){
    var self = this;
    self._ary.push({
        "priority" : priority,
        "elm"      : elm
    });
    self._build();
}
/**
 * BinaryHeap::changePriority
 * 要素の優先度を変更する
 * @param {Object} elm
 * @param {int} priority
 */
BinaryHeap.prototype.changePriority = function(elm, priority){
    var self = this;
    var ary  = self._ary;
    for(var i = 0; i < ary.length; i++){
        if(elm === ary[i]["elm"]){
            ary[i]["priority"] = priority;
            self._build();
            return true;
        }
    }
    return false;
}
/**
 * BinaryHeap::getPrior
 * 優先度の高い要素を取得する
 */
BinaryHeap.prototype.getPrior = function(){
    var self = this;
    var elm  = self._ary.shift();
    self._build();
    return elm["elm"];
}
/**
 * BinaryHeap::getList
 * ヒープを返す
 */
BinaryHeap.prototype.getList = function(){
    var self = this;
    return self._ary;
}

/**
 * BinaryHeap::inQueue
 * ヒープ内で探索
 */
BinaryHeap.prototype.inQueue = function(v){
    var self = this;
    for(var i = 0; i < self._ary.length; i++){
        if(self._ary[i]['elm'] === v){
            return true;
        }
    }
    return false;
}

inQueueメッソドを効率よくできないかな・・・(●´⌓`●)

three.jsを通してWebGLを使う


※Google Chrome以外のブラウザでは正確に表示されない部分があります。

■サンプルコード

サンプルコードを大体そのまま動かしながらコードを理解してみる。

(function(){
    var camera, scene, renderer,
    geometry, material, mesh;
    init();
    animate();
    function init() {
        camera = new THREE.Camera(75, window.innerWidth / window.innerHeight, 1, 10000);
        camera.position.z = 1000;
        scene = new THREE.Scene();
        geometry = new THREE.Cube(200, 200, 200 );
        material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
        mesh = new THREE.Mesh(geometry, material);
        scene.addObject(mesh);
        var canvas = document.getElementById('canvas2011030601');
        renderer = new THREE.CanvasRenderer();
        renderer.setSize(canvas.offsetWidth, 300);
        canvas.appendChild(renderer.domElement);
    }
    function animate() {
        // Include examples/js/RequestAnimationFrame.js for cross-browser compatibility.
        requestAnimationFrame(animate);
        render();
    }
    function render() {
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.02;
        renderer.render(scene, camera);
    }
})();

CanvasRendererとあるようにどうやら普通にcanvasで描画しているようだ。

■サンプルコード

以下のようにアレンジしてWebGLRendererを使うようにしてみる。

(function(){
    var camera;
    var scene;
    var renderer;
    var mesh;
    // initialize
    (function(){
        camera = new THREE.Camera(75, window.innerWidth / window.innerHeight, 1, 10000);
        camera.position.z = 1000;
        scene = new THREE.Scene();
        mesh = new THREE.Mesh(
            new THREE.Cube(200, 200, 200),
            new THREE.MeshBasicMaterial({
                "color"     : 0xff0000,
                "wireframe" : true
            })
        );
        scene.addObject(mesh);
        var canvas = document.getElementById('canvas2011030602');
        renderer = new THREE.WebGLRenderer();
        renderer.setSize(canvas.offsetWidth, 300);
        canvas.appendChild(renderer.domElement);
    })();
    // animation
    setInterval(
        function(){
            mesh.rotation.x += 0.02;
            renderer.render(scene, camera);
        },
        1000 / 60
    );
});

以下のように表示される。

アンチエイリアスがないのが気になる。 しかしドキュメントが見当たらなかったのでWebGLRenderer.jsを開いてみた。

_antialias = parameters.antialias !== undefined ? parameters.antialias : false,

75行目あたりに上述の記述があるので、以下のようにオプションを指定した。

(function(){
    var camera;
    var scene;
    var renderer;
    var mesh;
    // initialize
    (function(){
        camera = new THREE.Camera(75, window.innerWidth / window.innerHeight, 1, 10000);
        camera.position.z = 1000;
        scene = new THREE.Scene();
        mesh = new THREE.Mesh(
            new THREE.Cube(200, 200, 200),
            new THREE.MeshBasicMaterial({
                "color"     : 0xff0000,
                "wireframe" : true
            })
        );
        scene.addObject(mesh);
        var canvas = document.getElementById('canvas2011030603');
        renderer = new THREE.WebGLRenderer({"antialias" : true});
        renderer.setSize(canvas.offsetWidth, 300);
        canvas.appendChild(renderer.domElement);
    })();
    // animation
    setInterval(
        function(){
            mesh.rotation.x += 0.02;
            renderer.render(scene, camera);
        },
        1000 / 60
    );
});

以下のようにアンチエイリアスがかかった。但し、Firefoxではアンチエイリアスがかからなかった。

meshを以下のように記述することでテクスチャを貼ることができる。

mesh = new THREE.Mesh(
    new THREE.Cube(
        200,
        200,
        200,
        3,
        3,
        3,
        [
            new THREE.MeshBasicMaterial({map: THREE.ImageUtils.loadTexture('/img/lock.png')}), // right
            new THREE.MeshBasicMaterial({map: THREE.ImageUtils.loadTexture('/img/face.png')}), // left
            new THREE.MeshBasicMaterial({map: THREE.ImageUtils.loadTexture('/img/face.png')}), //top
            new THREE.MeshBasicMaterial({map: THREE.ImageUtils.loadTexture('/img/face.png')}), // bottom
            new THREE.MeshBasicMaterial({map: THREE.ImageUtils.loadTexture('/img/face.png')}), // back
            new THREE.MeshBasicMaterial({map: THREE.ImageUtils.loadTexture('/img/face.png')}) // front
        ]
    ),
    new THREE.MeshFaceMaterial()
);

以下のように表示される。

現時点でWebGLに対応しているPCブラウザはGoogle Chrome、Firefoxとなる。残念ながらスマートフォンではandroidのデフォルトのブラウザやMobile SafariでWebGLを見ることはできず、android版のFirefoxでは見ることができる。ちなみにWebGLを使うとMacBookが熱々になる(●´⌓`●)