imHo RSSフィード

2010-05-12

Processing(Java)で.objファイル読み込み

.plyファイルはマテリアルとかなくて残念。てことで.objファイル。Crytek Sponzaとか。

// .objファイルローダー
class OBJLoader {
  class Material {
    float[] ambient, diffuse, specular, emissive;
    float shininess;
    String diffusemap;
    String bumpmap;
  }

  class Mesh {
    ArrayList faces = new ArrayList();
    Material material;
  }

  ArrayList m_vertices = new ArrayList();
  ArrayList m_normals = new ArrayList();
  ArrayList m_uvs = new ArrayList();
  ArrayList m_mesh = new ArrayList();
  HashMap m_mtllib = new HashMap();

  boolean load(String fn) {
    BufferedReader reader = createReader(fn);
    try {
      if (reader == null)  return false;
      if (!read_data(reader, fn))  return false;
      return true;
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    }
  }

  boolean read_data(BufferedReader reader, String fn) throws IOException {
    String objname = null;
    Mesh mesh = new Mesh();
    for (;;) {
      String line = reader.readLine();
      if (line == null)  break;

      String[] m = line.trim().split("\\s+", 0);
      if (m == null)  continue;
      if (m[0].equals("v")) {  // 頂点座標
        float[] vtx = str2floats(m);
        m_vertices.add(vtx);
      } else if (m[0].equals("vn")) {  // 法線
        float[] nrm = str2floats(m);
        float rr = 0;
        for (int i = 0; i < nrm.length; ++i)  rr += sq(nrm[i]);
        float invr = 1.0 / sqrt(rr);
        for (int i = 0; i < nrm.length; ++i)  nrm[i] *= invr;
        m_normals.add(nrm);
      } else if (m[0].equals("vt")) {  // UV座標
        float[] uv = str2floats(m);
        m_uvs.add(uv);
      } else if (m[0].equals("f")) {  // フェース
        int[][] face = new int[m.length - 1][];
        for (int i = 1; i < m.length; ++i) {
          String[] idxs = m[i].split("/", 0);
          int[] iii = new int[idxs.length];
          for (int j = 0; j < idxs.length; ++j) {
            int x = int(idxs[j]);
            if (x > 0)       x = x - 1;                  // 絶対指定
            else if (x < 0)  x = m_vertices.size() + x;  // 相対指定
            else             x = -1;                     // 0: 無効
            iii[j] = x;
          }
          face[i - 1] = iii;
        }
        mesh.faces.add(face);
      } else if (m[0].equals("mtllib")) {  // マテリアルライブラリ
        String dir = new File(fn).getParent();
        for (int i = 1; i < m.length; ++i) {
          String mfn = m[i];
          if (!load_mtllib(dir + "/" + mfn)) {
            return false;
          }
        }
      } else if (m[0].equals("usemtl")) {  // マテリアル
        add_mesh(mesh);
        mesh = new Mesh();
        String name = m[1];
        if (m_mtllib.containsKey(name)) {
          mesh.material = (Material)m_mtllib.get(name);
        }
      } else {
        // skip
      }
    }
    add_mesh(mesh);
    return true;
  }

  private boolean load_mtllib(String fn) throws IOException {
    BufferedReader reader = createReader(fn);
    if (reader == null)  return false;

    String mtlname = null;
    Material material = null;
    for (;;) {
      String line = reader.readLine();
      if (line == null)  break;

      String[] m = line.trim().split("\\s+", 0);
      if (m[0].equals("newmtl")) {  // 頂点座標
        if (material != null)  add_material(mtlname, material);
        mtlname = m[1];
        material = new Material();
      } else if (m[0].equals("Ka")) {  // アンビエント
        material.ambient = str2floats(m);
      } else if (m[0].equals("Kd")) {  // ディフューズ
        material.diffuse = str2floats(m);
      } else if (m[0].equals("Ks")) {  // スペキュラー
        material.specular = str2floats(m);
      } else if (m[0].equals("Ke")) {  // エミッシブ
        material.emissive = str2floats(m);
      } else if (m[0].equals("map_Ka")) {  // ディフューズマップ
        material.diffusemap = m[1];
      } else if (m[0].equals("map_bump")) {  // バンプマップ
        material.bumpmap = m[1];
      } else {
        // なし
      }
    }
    if (material != null)  add_material(mtlname, material);
    return true;
  }

  private void add_material(String name, Material material) {
    m_mtllib.put(name, material);
  }

  private void add_mesh(Mesh mesh) {
    m_mesh.add(mesh);
  }

  private float[] str2floats(String words[]) {
    float[] a = new float[words.length - 1];
    for (int i = 1; i < words.length; ++i)  a[i - 1] = float(words[i]);
    return a;
  }
}

これを使って、ProcessingのOpenGLで表示するテスト:

import processing.opengl.*;

class Model extends OBJLoader {
  class Texture {
    PImage img;
    void bind() {
      texture(img);
    }
    float s(float u)  { return u * img.width; }
    float t(float v)  { return v * img.height; }
  }

  HashMap m_textures = new HashMap();

  boolean load(String fn) {
    if (!super.load(fn))  return false;
    for (Iterator it = m_mtllib.values().iterator(); it.hasNext(); ) {
      Material material = (Material)it.next();
      if (material.diffusemap != null)
        load_texture(material.diffusemap, fn);
    }
    return true;
  }

  void draw() {
    for (Iterator it = m_mesh.iterator(); it.hasNext(); ) {
      OBJLoader.Mesh mesh = (OBJLoader.Mesh)it.next();
      beginShape(TRIANGLES);
      Model.Texture tex = null;
      if (mesh.material != null) {
        int r = constrain(floor(mesh.material.diffuse[0] * 255), 0, 255);
        int g = constrain(floor(mesh.material.diffuse[1] * 255), 0, 255);
        int b = constrain(floor(mesh.material.diffuse[2] * 255), 0, 255);
        fill(r, g, b);
  
        tex = get_texture(mesh.material.diffusemap);
        if (tex != null) {
          tex.bind();
        }
      }
      for (int i=0; i<mesh.faces.size(); ++i) {
        int[][] face = (int[][])mesh.faces.get(i);
        for (int j=2; j<face.length; ++j) {
          drawvertex(face[0],   tex);
          drawvertex(face[j-1], tex);
          drawvertex(face[j],   tex);
        }
      }
      endShape();
    }
  }

  void drawvertex(int[] vinfo, Model.Texture tex) {
    float[] vtx = (float[])m_vertices.get(vinfo[0]);
    if (vinfo.length > 2 && vinfo[2] >= 0) {  // 法線がある
      float[] nrm = (float[])m_normals.get(vinfo[2]);
      normal(nrm[0], nrm[1], nrm[2]);
    }
    if (vinfo.length > 1 && vinfo[1] >= 0 && tex != null) {  // UVがある
      float[] uv = (float[])m_uvs.get(vinfo[1]);
      vertex(vtx[0], vtx[1], vtx[2], tex.s(uv[0]), tex.t(uv[1]));
    } else {
      vertex(vtx[0], vtx[1], vtx[2]);
    }
  }

  Texture get_texture(String name) {
    if (m_textures.containsKey(name)) {
      return (Texture)m_textures.get(name);
    } else return null;
  }

  private void load_texture(String mtlfn, String mdlfn) {
    if (!m_textures.containsKey(mtlfn)) {
      String dir = new File(mdlfn).getParent();
      String name = new File(mtlfn).getName();
      PImage img = loadImage(dir + "/" + name);
      if (img != null) {
        Texture tex = new Texture();
        tex.img = img;
        m_textures.put(mtlfn, tex);
      }
    }
  }
}

Model model;

void setup() {
  size(640, 480, OPENGL);

  model = new Model();
  boolean r = model.load("sponza/Sponza.obj");
  if (r) {
    println("model load succeeded");
  } else {
    println("model load failed");
  }
}

float rotx = 0;
float roty = 0;
float dist = 100;

void mouseDragged() {
  switch (mouseButton) {
  case LEFT:
    float rate = 0.01;
    rotx += (pmouseY - mouseY) * rate;
    roty += (pmouseX - mouseX) * rate;
    break;
  case RIGHT:
    dist += dist * (pmouseY - mouseY) * 4 / height;
    break;
  }
}

void draw() {
  lights();
  background(color(0, 0, 128));

  float cameraY = height/2.0;
  float fov = radians(60);
  float cameraZ = cameraY / tan(fov / 2.0);
  float aspect = float(width)/float(height);
  perspective(fov, aspect, cameraZ/10.0, cameraZ*10.0);

  translate(width/2, height/2, cameraZ - dist);
  rotateX(PI);
  rotateX(rotx);
  rotateY(roty);

  noStroke();
  model.draw();
  stroke(255, 0, 0);  line(0,0,0, 100,0,0);
  stroke(0, 255, 0);  line(0,0,0, 0,100,0);
  stroke(0, 0, 255);  line(0,0,0, 0,0,100);
}

f:id:mokehehe:20100512232035j:image:right

  • objファイルフォーマットはこのへん、でもあまり見てない
  • ProcessingのloadImage()はjpegやpngだけじゃなく、tgaやbmpも対応してる、すげぇ!
  • CrytekのSponzaのスクリーンショットでは青や緑の布もかかってるのに、データでは赤しかない、クソッ!
  • 真ん中の布が邪魔
  • UVが狂ってるのかクランプされてるのかよくわからない
  • ノーマルマップは試してない
  • .objファイルフォーマットは行単位で、先頭の文字列で区別できるとか、シンプルでいいな
    • 簡潔なファイルフォーマットは生き残るね
    • 階層構造やモーションにもこういう、テキスト形式でシンプルなファイルフォーマットがあるといいな
  • これでメタセコで作られたデータとか出せるんじゃ…と思ったら、フリー版では.objに出力できなかった、残念…
  • Processingは、テクスチャがない状態で vertex() でUVを設定するとエラーが出て止まってしまう、うっとおしい
  • しかしJava、というかGCのある言語は後始末書かなくていいのは滅茶苦茶楽だな

とおりすがりとおりすがり 2011/03/17 09:27 わかりにくソースだなぁ。

mokehehemokehehe 2011/03/17 20:09 好きに直してくれていいよ!

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/mokehehe/20100512/obj
リンク元