2011年5月31日火曜日

AndroidでMetasequoiaのmqoを読み込むコード その2 Textureクラス

大分空きましたが続きー

VBOとTextureをワンセットにすることで、DirectXで言うサブセットみたいな処理ができるので、Textureクラスも当然必要です(テクスチャの切り替えを最小限にするため)

package com.dividebyzero.KszGameBase;

import java.util.HashMap;

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

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.Log;

/**
 * テクスチャクラス
 * @author ksz
 *
 */
public class Texture {
 public int resID, texID, w, h;
 private static final RenderParam param = new RenderParam();

 /**
  * @param resID リソースID
  * @param texID OpenGLでのテクスチャID
  * @param w 横幅
  * @param h 縦幅
  */
 public Texture(int resID, int texID, int w, int h) {
  this.resID = resID;
  this.texID = texID;
  this.w = w;
  this.h = h;
 }

 /**
  * staticのrenderを呼びやすくしただけ
  */
 public void render(float x,float y){
  render(this,x,y,0,param.init());
 }
 
 /**
  * staticのrenderを呼びやすくしただけ
  */
 public void render(float x, float y, RenderParam param) {
  render(this, x, y, 0, param);
 }

 /**
  * staticのrenderを呼びやすくしただけ
  */
 public void render(float x, float y, float z, RenderParam param) {
  render(this, x, y, z, param);
 }

 /**
  * staticのポリラインを呼びやすくしただけ
  */
 public void renderPolyLine(float sx, float sy, float ex, float ey, RenderParam param) {
  renderPolyLine(this, sx, sy, ex, ey, param);
 }

 /**
  * テクスチャのバインド
  */
 public void bind() {
  setTexture(texID);
 }

 //////////////////ここから管理ゾーン
 public static HashMap<Integer, Texture> texMap = new HashMap<:Integer, Texture>();

 /**
  * リソースIDではなく、文字列からテクスチャインスタンスを作成
  * @param resName 読み込みたい文字列 "***.png"のようにしても、拡張子部分は捨てられる
  * @return 作成されたテクスチャインスタンス
  */
 public static Texture get(String resName) {
  Context context = Global.view.getContext();
  int point = resName.lastIndexOf(".");
  if (point != -1) {
   resName = resName.substring(0, point);
  }
  Log.v("debug", resName);
  int id = context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
  return get(id);
 }

 /**
  * リソースIDからテクスチャインスタンスを作成
  * @param resID 読み込みたいリソース
  * @return 作成されたテクスチャインスタンス。既に読み込まれている場合はそのインスタンス
  */
 public static Texture get(int resID) {
  if (texMap.containsKey(resID)) {
   return texMap.get(resID);
  }

  GL10 gl = Global.gl;
  //リソースBMPを読み込む
  BitmapFactory.Options opt = new BitmapFactory.Options();
  opt.inScaled = false; //勝手に拡大縮小するので止める
  Bitmap bitmap = BitmapFactory.decodeResource(Global.view.getResources(), resID, opt);
  //グラボのテクスチャとして登録
  int[] texID = new int[1]; //グラボからテクスチャIDを受け取るための変数
  gl.glGenTextures(1, texID, 0); //グラボから使ってないテクスチャIDを取得
  Texture.setTexture(texID[0]);
  //BMPをテクスチャに割り当てる
  GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
  //テクスチャパラメータをセット
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, //拡大の時は
    GL10.GL_LINEAR); //線形補間
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, //縮小の時は
    GL10.GL_LINEAR); //線形補間
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, //UVがはみ出したときどうしますか
    GL10.GL_CLAMP_TO_EDGE); //端っこで止める
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, //UVがはみ出したときどうしますか
    GL10.GL_CLAMP_TO_EDGE); //端っこで止める

  Texture tex = new Texture(resID, texID[0], bitmap.getWidth(), bitmap.getHeight());
  bitmap.recycle();
  texMap.put(resID, tex);
  return tex;
 }

 private static Vbo vbo = null;
 private static int currentTexID = -1;

 /**
  * テクスチャをOpenGLにセット
  * 同じテクスチャが連続でセットされないようにしている
  * @param id セットしたいテクスチャ
  */
 public static void setTexture(int id) {
  if (currentTexID != id) {
   currentTexID = id;
   Global.gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
  }
 }

 /**
  * 指定されたテクスチャを描画 z軸が無いVer
  * @param tex 描画したいテクスチャ
  * @param x 座標x
  * @param y 座標y
  * @param param 描画パラメータ
  */
 public static void render(Texture tex, float x, float y, RenderParam param) {
  render(tex, x, y, 0, param);
 }

 /**
  * 指定されたテクスチャを描画
  * @param tex 描画したいテクスチャ
  * @param x 座標x
  * @param y 座標y
  * @param z 座標z 2Dゲームの場合は0で
  * @param param 描画パラメータ 
  */
 public static void render(Texture tex, float x, float y, float z, RenderParam param) {
  if (vbo == null) {
   //頂点4つ分用意
   float v[] = {
     //頂点
     0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0,
   };
   float t[] = {
     //UV
     0, 0, 0, 1, 1, 0, 1, 1,
   };

   vbo = new Vbo();
   vbo.makeBuffer(v, t);
  }
  vbo.bindVBO();
  GL11 gl = Global.gl;

  setTexture(tex.texID);
  gl.glColor4f(param.r, param.g, param.b, param.a);

  if (param.cw + param.ch < 0) {
   param.cw = (float) tex.w;
   param.ch = (float) tex.h;
  }
  if (param.isCenterOrigin) {
   if (param.isDestsize) {
    param.ox = param.dw / 2;
    param.oy = param.dh / 2;
   } else {
    param.ox = param.cw * Math.abs(param.sx) / 2;
    param.oy = param.ch * Math.abs(param.sy) / 2;
   }
  }
  if (RenderParam.oldAddAlphaMode != param.isAddAlpha) {
   if (param.isAddAlpha) {
    gl.glDisable(GL10.GL_DEPTH_TEST);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
   } else {
    gl.glEnable(GL10.GL_DEPTH_TEST);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
   }
  }
  RenderParam.oldAddAlphaMode = param.isAddAlpha;

  gl.glMatrixMode(GL10.GL_TEXTURE);
  gl.glLoadIdentity();
  gl.glTranslatef(param.cx + param.cw / tex.w * param.xindex, param.cy + param.ch / tex.h * param.yindex, 0);
  gl.glScalef(param.cw / tex.w, param.ch / tex.h, 1);

  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glPushMatrix(); //カメラ行列を退避

  //移動させつつ、原点指定しつつ
  if (param.isDestsize) {
   gl.glTranslatef(0.5f * param.dw - param.ox + x, 0.5f * param.dh - param.oy + y, z);
  } else {
   gl.glTranslatef(0.5f * param.cw * Math.abs(param.sx) - param.ox + x, 0.5f * param.ch * Math.abs(param.sy)
     - param.oy + y, z);
  }
  gl.glRotatef(param.ax, 0, 0, 1); //回転(なぜかラジアンじゃない)
  if (param.isDestsize) {
   gl.glScalef(param.dw, param.dh, 1); //拡大縮小
  } else {
   gl.glScalef(param.sx * param.cw, param.sy * param.ch, 1); //拡大縮小
  }
  gl.glTranslatef(-0.5f, -0.5f, 0); //中心を原点に回転するための小細工

  gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); //横1縦1の頂点描画

  gl.glPopMatrix(); //カメラ行列を戻す
 }

 /**
  * ポリライン(テクスチャを線のように描画)
  * @param tex 描画したいテクスチャ
  * @param sx 始点x
  * @param sy 始点y
  * @param ex 終点x
  * @param ey 終点y
  * @param param パラメータ、幅など
  */
 void renderPolyLine(Texture tex, float sx, float sy, float ex, float ey, RenderParam param) {
  Vec3 tang = new Vec3(ex - sx, ey - sy, 0);
  float angle = (float) Math.atan2(tang.y, tang.x) * Util.TOANGLE;
  param.setDestSize(Math.abs(tang.getLength()), Math.abs(param.sx)).setAngle(angle);
  param.setCenterOrigin(true);
  render(tex, sx + tang.x / 2, sy + tang.y / 2, param);
 }

 /**
  * 指定されたリソースを描画 
  * 指定されたリソースがまだ読み込まれていない場合は読み込まれる
  * @param resID R.drawable.iconなど、リソースのIDを直指定
  * @param x 描画位置x
  * @param y 描画位置y
  * @param param RenderParamのインスタンスによって描画パラメータを操作
  */
 public static void render(int resID, int x, int y, RenderParam param) {
  render(get(resID), x, y, param);
 }

 /**
  * 管理されているテクスチャを全て開放
  */
 public static void release() {
  int textureID[] = new int[1];
  for(Texture tex:texMap.values()){
   textureID[0] = tex.texID;
   Global.gl.glDeleteTextures(1, textureID, 0);
  }
  texMap.clear();
  texMap = null;  
 }

 /**
  * 画面クリア処理
  * Textureクラスに入れるのが適当がどうかは微妙なところ
  * 深度を有効化しないと、深度バッファのクリアがうまくいかない場合があるようなので、毎回入れてます。 負荷だけど。
  */
 public static void clear() {  
  Global.gl.glEnable(GL10.GL_DEPTH_TEST);
  Global.gl.glDepthMask(true); // 深度バッファ書き込み有効
  Global.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 }

 /**
  * 画面をクリアします
  * @param r 赤(0.0f~1.0f)
  * @param g 緑(0.0f~1.0f)
  * @param b 青(0.0f~1.0f)
  * @param a アルファ値(0.0f~1.0f)
  */
 public static void clear(float r,float g,float b,float a) {
  Global.gl.glClearColor(r,g,b,a);
  clear();
 }
 /**
  * 画面をクリアします
  * @param r 赤(0~255)
  * @param g 緑(0~255)
  * @param b 青(0~255)
  * @param a アルファ値(0~255)
  */
 public static void clear(int r,int g,int b,int a) {
  Global.gl.glClearColor(r/255.0f,g/255.0f,b/255.0f, a / 255.0f);
  clear();
 }
}

しかし、ソースのほとんどが2D用だっていうのは僕と君だけの秘密だぞ。

2011年5月25日水曜日

AndroidでMetasequoiaのmqoを読み込むコード その1 VBO

以前書いた記事でmqo読んでるぜーって書いたらなんだか奇特な方がソースを晒してくれってな事だったので、ざっと。

でも、結構絡み合ってるんですよ? これが?

まず、VertexObjectで描画が基本なので、VBOクラスから。

import java.nio.FloatBuffer;
import java.util.ArrayList;

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

/**
 * VertexBufferObjectクラス
 * @author ksz
 */
public class Vbo {
 public float r=1,g=1,b=1,a=1,diffuse=1,ambient=1;
 public int vSize, tSize;
 private int vboID = -1;
 private static int oldID = -1;

 /**
  * GLにVBOをセット
  */
 public void bindVBO() {
  if (oldID == vboID) return; //既に同じバッファがセットされていたら無視
  if (vboID < 0) return; //makeBufferされてないようなので無視

  oldID = vboID;
  GL11 gl = Global.gl;
  gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vboID);
  gl.glVertexPointer(3, GL11.GL_FLOAT, 0, 0); //頂点バッファ

  if (tSize != 0) {
   gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, vSize); //UVバッファ
  }
 }

 /**
  * VBOを描画します
  */
 public void draw() {
  GL11 gl = Global.gl;
  
  //テクスチャ行列を外す
  gl.glMatrixMode(GL10.GL_TEXTURE);
  gl.glLoadIdentity();

  gl.glColor4f(r, g, b, a);

  bindVBO();
  
  //vSizeは頂点配列のバイト数なので、floatにするのに/4 TRIANGLESなので3角ポリで/3 で/12
  gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vSize / 12);
 }

 /**
  * 頂点とUVの配列からVBO作成
  * @param v 頂点配列
  * @param t UV配列
  */
 public void makeBuffer(float v[], float t[]) {
  vSize = v.length;
  tSize = 0;
  if (t != null && t.length != 0) {
   tSize = t.length;
  }
  makeBuffer(v,vSize, t,tSize);  
 }

 /**
  * 頂点とUVの配列からVBO作成(サイズ指定版)
  * @param v  頂点配列
  * @param vCnt 頂点配列の有効要素数
  * @param t  UV配列
  * @param tCnt UV配列の有効要素数
  */
 public void makeBuffer(float v[],int vCnt, float t[],int tCnt) {
  GL11 gl = Global.gl;
  vSize = vCnt * 4;
  tSize = tCnt * 4;

  int vbo[] = new int[1];
  gl.glGenBuffers(1, vbo, 0);
  gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo[0]);
  gl.glBufferData(GL11.GL_ARRAY_BUFFER, vSize + tSize, null, GL11.GL_STATIC_DRAW);
  gl.glBufferSubData(GL11.GL_ARRAY_BUFFER, 0, vSize, FloatBuffer.wrap(v));
  if (tSize != 0) {
   gl.glBufferSubData(GL11.GL_ARRAY_BUFFER, vSize, tSize, FloatBuffer.wrap(t));
  }
  vboID = vbo[0];
 }
}

ちなみに、ライティングは無視してるので、勝手に拡張してね。
Global.gl とかがさも当然のように使われてますが、GL11インスタンスはいろんなところから使うので、グローバルっぽく使うためにpublic static な領域にGl11インスタンスを渡してある前提で。

まだまだ続きます。

2011年5月19日木曜日

ARの前にカメラのプレビュー画像とOpenGLの画像を重ねてみた


カメラ用のSurfaceView作って、FrameLayoutでOpenGLのViewと重ねるだけじゃん? って思ってたら結構はまりました。

いろんなところですでに言われてますが、

①普通に考えるとカメラの画像にOpenGLを重ねるので、
カメラビューの追加 → OpenGLビューの追加
となると思いきや、
OpenGLビューの追加 → カメラビューの追加
にしないといけない罠
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  FrameLayout layout = new FrameLayout(this);
  setContentView(layout);  
  layout.addView(new GameView(this, new OpeningScene(), 640, 480));
  layout.addView(new CameraSurfaceView(this));
 }

②OpenGLの方のピクセルフォーマットを透過にしなければいけない罠+ChoosConfigの時にEGL_ALPHA_SIZEを指定しないと機種によっては表示がバグる罠。
※でもなんか、PixelFormat.RGBA_8888でも大丈夫だった、うん?
  getHolder().setFormat(PixelFormat.RGBA_8888);
  getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
  getHolder().addCallback(this);
  int[] spec = {
   EGL10.EGL_ALPHA_SIZE,1,
   EGL10.EGL_DEPTH_SIZE,1,
   EGL10.EGL_NONE
  }; 
  EGLConfig[] configs = new EGLConfig[1];
  egl.eglChooseConfig(display, spec, configs, 1, new int[1]);

③glClearColorのアルファの指定を1以外にしないとカメラビューが映らない罠
 public static void clear() {  
  Global.gl.glClearColor(1,1,1,0);
  Global.gl.glEnable(GL10.GL_DEPTH_TEST);
  Global.gl.glDepthMask(true); // 深度バッファ書き込み有効
  Global.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 }

ARとか程遠い。

2011年5月12日木曜日

縦画面対応


ちなみに前回書き忘れましたがモデルはココのをお借りしております。

2D画像の板ポリを球っぽく並べたまではいいんですが、実はこいつらビルボード化してないのですよな。
ちゅーか、ビルボードの為の計算量がもったいないっちゅーか。 ポイントスプライトってないのかなぁ。

2011年5月6日金曜日

Androidでメタセコイアのmqoファイルを読み込んでみるテスト

エミュレータではとてもじゃないけど開発する気にならない3Dですが、実機(IS01・Life Touch Note)ではサクサク動くので、3Dにも手を出してみることに。

最初はXファイル読み込ませようかとも思ったんですが、あのフォーマットは手に負えないのでメタセコイア(Metasequoia)のtxtのmqoファイルを読み込ます事に。
理論上は完璧だと思ってたら、穴が空きまくったり(DEPTH_SIZEが問題だった)
2Dと両用を考えてたんだけど(3Dだけど、スコア表示は2Dなど)Lief Touch Noteの射影マトリクスのスタックの数が1か2ぐらいしかなくて、glPushMatrix()・glPopMatrix()が役立たずだったりしましたとさ。

ソースコードはもうちょっとリファクタリングしたら晒したいなぁ。