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); }
- objファイルフォーマットはこのへん、でもあまり見てない
- ProcessingのloadImage()はjpegやpngだけじゃなく、tgaやbmpも対応してる、すげぇ!
- CrytekのSponzaのスクリーンショットでは青や緑の布もかかってるのに、データでは赤しかない、クソッ!
- 真ん中の布が邪魔
- UVが狂ってるのかクランプされてるのかよくわからない
- ノーマルマップは試してない
- .objファイルフォーマットは行単位で、先頭の文字列で区別できるとか、シンプルでいいな
- 簡潔なファイルフォーマットは生き残るね
- 階層構造やモーションにもこういう、テキスト形式でシンプルなファイルフォーマットがあるといいな
- これでメタセコで作られたデータとか出せるんじゃ…と思ったら、フリー版では.objに出力できなかった、残念…
- Processingは、テクスチャがない状態で vertex() でUVを設定するとエラーが出て止まってしまう、うっとおしい
- しかしJava、というかGCのある言語は後始末書かなくていいのは滅茶苦茶楽だな
トラックバック - http://d.hatena.ne.jp/mokehehe/20100512/obj
リンク元
- 53 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCYQFjAA&url=http://d.hatena.ne.jp/mokehehe/20100512/obj&ei=W-FGT8DpCsfsmAXoyaWVDg&usg=AFQjCNGZb6s-ch-fZoGdo0jdwiE1dclluA&sig2=d2BEuZn2_p9Wkrg1ruKq0A
- 41 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cts=1331175888958&ved=0CDIQFjAB&url=http://d.hatena.ne.jp/mokehehe/20100512/obj&ei=uiFYT-GcD-7ymAXbp8WsDw&usg=AFQjCNGZb6s-ch-fZoGdo0jdwiE1dclluA
- 39 https://www.google.co.jp/
- 35 http://www.google.co.jp/url?sa=t&rct=j&q=objファイル読み込み&source=web&cd=5&ved=0CEAQFjAE&url=http://d.hatena.ne.jp/mokehehe/20100512/obj&ei=pf-DTuSGFcKAmQXRg5g8&usg=AFQjCNGZb6s-ch-fZoGd
- 34 http://www.google.co.jp/url?sa=t&rct=j&q=processing ファイル&source=web&cd=2&ved=0CDcQFjAB&url=http://d.hatena.ne.jp/mokehehe/20100512/obj&ei=Hb2iTq-tJO6UmQWr--GgCQ&usg=AFQjCNGZb6s-ch-fZoGdo0jdwiE1dclluA&sig2=XrSQ
- 31 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&cts=1331368771553&ved=0CDgQFjAC&url=http://d.hatena.ne.jp/mokehehe/20100512/ply&ei=IxNbT7e1A-_MmAWc2vXBDw&usg=AFQjCNFL516NNXjMh6oH-5hXoTk-LR0cmA&sig2=306vrTtdkPNfyholiJBLpw
- 26 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&frm=1&source=web&cd=1&cts=1331790005035&ved=0CDEQFjAA&url=http://d.hatena.ne.jp/mokehehe/20100512/ply&ei=hIBhT-GeO4_4mAWmvZCbCA&usg=AFQjCNFL516NNXjMh6oH-5hXoTk-LR0cmA
- 23 http://www.google.co.jp/url?sa=t&rct=j&q=plyファイル&source=web&cd=4&ved=0CD8QFjAD&url=http://d.hatena.ne.jp/mokehehe/20100512/ply&ei=Qi6ETt6IB-XXmAWi8tD5Dw&usg=AFQjCNFL516NNXjMh6oH-5hXoTk-LR0cmA
- 21 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4ADRA_jaJP381JP382&q=java+ファイル読み込み+arraylist
- 19 http://www.google.co.jp/url?sa=t&rct=j&q=processing obj&source=web&cd=2&ved=0CCQQFjAB&url=http://d.hatena.ne.jp/mokehehe/20100512/obj&ei=5V2lTviGNa_umAWamICcCQ&usg=AFQjCNGZb6s-ch-fZoGdo0jdwiE1dclluA&sig2=LT-q79UkvEk0tAI8lMBTSg&cad=rja

