2011年6月20日月曜日

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

やっとメタセコファイルを読み込む所です。

とは言っても、テキストを1行づつ解釈していくだけなんですけど。

package com.dividebyzero.KszGameBase;

import java.io.BufferedReader;
import java.util.StringTokenizer;

import android.util.Log;

public class MQOLoader extends MeshLoader{
 private Subset[] subset;
 private int materialCnt;

 @Override
 public Mesh load(BufferedReader in) throws Exception {
  TokenReader tokenReader = new TokenReader(in);
  float v[] = null;
  int isVisible = 1;
  for (String token = tokenReader.getLine(); (token = tokenReader.getString()) != null;) {
   if ("visible".equals(token)) {
    isVisible = tokenReader.getInt();
    Log.v("debug", "visible:"+isVisible);
   }
   if (isVisible != 0) {
    if ("vertex".equals(token)) {
     v = vertexParse(tokenReader);
    } else if ("color".equals(token)) {
     Log.v("debug", "color finded.");
    } else if ("face".equals(token)) {
     faceParse(tokenReader, v);
    } else if ("Material".equals(token)) {
     materialParse(tokenReader);
    }
   }
  }
  //subset配列 で、Meshクラスを作る
  return new Mesh(subset);
 }

 private void materialParse(TokenReader in) throws Exception {
  Log.v("debug", "material finded.");
  String tToken;
  materialCnt = in.getInt() + 1;
  Log.v("debug", "material is " + materialCnt + ".");

  subset = new Subset[materialCnt];
  for (int i = 0; i < materialCnt; i++){
   subset[i] = new Subset();
  }
  tToken = in.getString();
  Subset mat;
  if ("{".equals(tToken)) {
   for (int i = 0; i < materialCnt; i++) {
    Log.v("debug", "material name is " + in.getString() + ".");
    mat = subset[i]; //ローカル化して速度を稼ぐ(雀の涙程度)
    while (!in.empty()) {
     tToken = in.getString();
     if ("tex".equals(tToken)) {
      mat.addTexture(Texture.get(in.getString2()));
     } else if ("col".equals(tToken)) {
      mat.r = in.getFloat();
      mat.g = in.getFloat();
      mat.b = in.getFloat();
      mat.a = in.getFloat();
     } else if ("dif".equals(tToken)) {
      mat.diffuse = in.getFloat();
     } else if ("amb".equals(tToken)) {
      mat.ambient = in.getFloat();
     }
    }
   }
  }
  //マテリアル無し用マテリアル
  mat = subset[materialCnt-1];
  mat.r = 0.5f;
  mat.g = 0.5f;
  mat.b = 0.5f;
  mat.a = 0.5f;
  mat.diffuse = 0.5f;
  mat.ambient = 0.5f;
 }

 private float[] vertexParse(TokenReader in) throws Exception {
  Log.v("debug", "vertex parse start.");
  String tToken;
  float[] tVertices;
  int tNumberOfVertex = in.getInt();
  Log.v("debug", "vertex is " + tNumberOfVertex + ".");
  tVertices = new float[tNumberOfVertex * 3];
  tToken = in.getString();
  if ("{".equals(tToken)) {
   for (int i = 0; i < tNumberOfVertex; i++) {
    tVertices[i * 3 + 0] = in.getFloat();
    tVertices[i * 3 + 1] = -in.getFloat();
    tVertices[i * 3 + 2] = -in.getFloat();
   }
   tToken = in.getString();
  }
  Log.v("debug", "vertex parse end.");
  return tVertices;
 }

 private void faceParse(TokenReader in, float v[]) throws Exception {
  Log.v("debug", "face finded.");

  String tToken;
  int tNumberOfFace = in.getInt();
  Log.v("debug", "face is " + tNumberOfFace + ".");

  tToken = in.getString();
  int vTemp[] = new int[4];
  int matNo = 0;
  if ("{".equals(tToken)) {
   for (int i = 0; i < tNumberOfFace; i++) {
    int vCnt = in.getInt(); //そのfaceが何頂点か。
    boolean isUV = false;
    boolean isMaterial = false;
    while (!in.empty()) {
     tToken = in.getString();
     if ("V".equals(tToken)) {
      for (int j = 0; j < vCnt; j++) {
       vTemp[j] = in.getInt();
      }
     } else if ("M".equals(tToken)) {
      //順番的にMがあとで来てしまうので、頂点をとっておいて、マテリアル番号毎に
      isMaterial = true;
      matNo = in.getInt();

      subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[1] * 3], v[vTemp[1] * 3 + 1], v[vTemp[1] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      if (vCnt == 4) {
       subset[matNo].addVertex(v[vTemp[3] * 3], v[vTemp[3] * 3 + 1], v[vTemp[3] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      }
     } else if ("UV".equals(tToken)) {
      float uvTemp[] = new float[8];
      isUV = true;
      switch (vCnt) {
       case 3: //三角ポリなので、そのまま
        for (int j = 0; j < 6; j++) {
         uvTemp[j] = in.getFloat();
        }
        subset[matNo].addUV(uvTemp[4], uvTemp[5]);
        subset[matNo].addUV(uvTemp[2], uvTemp[3]);
        subset[matNo].addUV(uvTemp[0], uvTemp[1]);
        break;
       case 4://4角ポリなので、分割して放り込む
        for (int j = 0; j < 8; j++) {
         uvTemp[j] = in.getFloat();
        }
        subset[matNo].addUV(uvTemp[4], uvTemp[5]);
        subset[matNo].addUV(uvTemp[2], uvTemp[3]);
        subset[matNo].addUV(uvTemp[0], uvTemp[1]);

        subset[matNo].addUV(uvTemp[6], uvTemp[7]);
        subset[matNo].addUV(uvTemp[4], uvTemp[5]);
        subset[matNo].addUV(uvTemp[0], uvTemp[1]);
        break;
      }
     }
    }
    //マテリアルが当たってなかった
    if(isMaterial == false){
     matNo = materialCnt - 1; //無名マテリアルに当てる

     subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
     subset[matNo].addVertex(v[vTemp[1] * 3], v[vTemp[1] * 3 + 1], v[vTemp[1] * 3 + 2]);
     subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
     if (vCnt == 4) {
      subset[matNo].addVertex(v[vTemp[3] * 3], v[vTemp[3] * 3 + 1], v[vTemp[3] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
     }
    }
    //UVが当たってなかった
    if (isUV == false) {
     subset[matNo].addUV(0, 0);
     subset[matNo].addUV(0, 0);
     subset[matNo].addUV(0, 0);
     if (vCnt == 4) {
      subset[matNo].addUV(0, 0);
      subset[matNo].addUV(0, 0);
      subset[matNo].addUV(0, 0);
     }
    }
   }
   tToken = in.getString();
  }
  Log.v("debug", "face parse end.");
  Log.v("debug", "v=" + subset[0].getVCnt());
 }
}

final class TokenReader {
 private BufferedReader mReader;

 private StringTokenizer mStringTokenizer;

 private boolean mIsInitialized;

 public TokenReader(BufferedReader bufferedReader) throws Exception {
  mReader = bufferedReader;
  mIsInitialized = refreshTokenizer();
 }

 private boolean refreshTokenizer() throws Exception {
  String line;
  while ((line = mReader.readLine()) != null)
   if (line.length() != 0 && line.charAt(0) != '#') {
    mStringTokenizer = new StringTokenizer(line, ",; \t()", false);
    return true;
   }
  return false;
 }

 public String next() throws Exception {
  if (mIsInitialized) {
   if (mStringTokenizer.hasMoreTokens()) {
    String token = mStringTokenizer.nextToken();
    return token;
   }
   if (!(mIsInitialized = refreshTokenizer())) return null;
   if (mStringTokenizer.hasMoreTokens()) return mStringTokenizer.nextToken();
  }
  return null;
 }

 public boolean empty() {
  return !mStringTokenizer.hasMoreTokens();
 }

 public float getFloat() throws Exception {
  return Float.parseFloat(next());
 }

 public int getInt() throws Exception {
  return Integer.parseInt(next());
 }

 public String getString() throws Exception {
  return next();
 }

 public String getString2() throws Exception {
  return next().replace('"', ' ').trim();
 }

 public String getLine() throws Exception {
  nextLine();
  return mReader.readLine();
 }

 public void nextLine() {
  while (mStringTokenizer.hasMoreTokens()) {
   mStringTokenizer.nextToken();
  }
 }

 public void close() throws Exception {
  mReader.close();
 }
}
ちゅーか、TokenReaderはどこかから拾った物です・・・。 どこだったかなぁ・・。 肝としては、全て3角ポリに変換するために、4角の面があった場合に
subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[1] * 3], v[vTemp[1] * 3 + 1], v[vTemp[1] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      if (vCnt == 4) {
       subset[matNo].addVertex(v[vTemp[3] * 3], v[vTemp[3] * 3 + 1], v[vTemp[3] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      }

のように、2つの3角ポリに変換をしているところでしょうか。
同じように、UVなんかも割り振ってあげる必要が。

5 件のコメント:

  1. 突然すみません。

    5月31日の記事 : Textureクラス
    renderPolyLineメソッド内の
    Vec3クラスの挙動と,Util.TOANGLEの値を教えていただけないでしょうか。よろしくお願い致します。

    返信削除
  2. お初です。

    まさかrenderPolyLineにツッコミが入るとは・・・。

    一応質問に答えると、Vec3クラスはただの3次元ベクトルのクラスなので、float3つを管理しているだけです。

    Util.TOANGLEは、ラジアンを角度に変換するために、定数で
    public static final float TOANGLE=57.2957795f;
    って宣言されてます。
    まぁ、180を円周率で割った値ですね。 毎回計算するのは無駄かなと。
    Math.toDegrees( rad ) 使ってくれた方がいいかもですが。

    そもそも、RenderParamクラスやらUtilクラスについて書いてないので、あれだけ見ても想像の域を超えないですね・・・。すいません・・・。

    返信削除
  3. お初です。ご回答ありがとうございます。

    せっかくお返事いただいたのですが、3Dプログラミングについての理解不足のために、今はモデルの表示の方法などに苦戦しているところです…。
    これについては、頑張って自身で解決したいと思います。

    また不明な点についてお尋ねする時もあると思いますが、その時はよろしくお願い致します。

    ありがとうございました。

    返信削除
  4. 僕に答えられる範囲であれば何でも答えますので、遠慮無くー。

    ちなみに3D表示は、最初はVBO(VertexBufferObject)を使わず、マテリアルや法線を無視して、頂点だけをGL_POINTS指定して描画するところから始めると良いと思います。

    返信削除
  5. はじめまして。

    AndroidでのMQOファイルの読み込みについてとても参考にさせていただいています。

    RenderParamクラスとVec3クラスについても是非公開していただきたいのですが可能でしょうか?

    できればプロジェクトを丸ごと公開していただけるととてもうれしいです。

    よろしくお願いします。

    返信削除