こういうかっこいい戦車も……
Metasequoia にプリセットされている「Tank」 |
Processing で表示した結果 |
ちなみにここだけの話、X ファイル内のジオメトリの頂点数とテクスチャのUV頂点数が一致していないと表示できないという問題点があるのですが、Metasequoia が吐く X ファイルでは特に目立った問題は起きていないため、思い切って無視しています。
それでも泥縄式に拡張しただけあって、いい感じにソースが汚くなって参りました。
【Xファイル解析関連のコード】
// ------------------------------------------------- // ================================================= // Xファイル形式で記述されたメッシュを表示するテスト // スタティックメッシュ篇やや不完全版 // ================================================= // ------------------------------------------------- // ================================================= // メッシュ class Mesh { List<Polygon> polygons; public Mesh() { polygons = new ArrayList<Polygon>(); } public void add(Polygon p) { polygons.add(p); } public Polygon getPolygon(int index) { return polygons.get(index); } public void render() { if(polygons == null) return; for(Polygon p : polygons) p.render(); } } // ================================================= // ポリゴン class Polygon { List<PVector> vertices; List<PVector> textureCoords; Material material; public Polygon() { vertices = new ArrayList<PVector>(); } public void addVertex(PVector v) { vertices.add(v); } public void addTextureCoord(PVector t) { if(textureCoords == null) textureCoords = new ArrayList<PVector>(); textureCoords.add(t); } public void setMaterial(Material m) { material = m; } public void render() { if(vertices == null) return; if(vertices.size() == 3) beginShape(TRIANGLES); else if(vertices.size() == 4) beginShape(QUADS); else beginShape(); pushStyle(); // マテリアルの設定 if(material != null) { noStroke(); fill(material.faceColor); shininess(material.power); specular(material.specularColor); emissive(material.emissiveColor); if(material.texture != null) { // テクスチャが存在する場合 texture(material.texture); } } for(int i = 0; i < vertices.size(); ++i) { PVector v = vertices.get(i); // 実際の頂点とテクスチャのUVが一致していない場合は表示できないよ if(textureCoords != null && textureCoords.size() == vertices.size() && material != null && material.texture != null) { PVector tex = textureCoords.get(i); textureMode(NORMALIZED); vertex(v.x, v.y, v.z, tex.x, tex.y); } else { vertex(v.x, v.y, v.z); } } popStyle(); endShape(); } } // ================================================= // マテリアル class Material { int faceColor; float power; int specularColor; int emissiveColor; PImage texture; public Material() { texture = null; } } // ================================================= // Xファイルローダクラス class Loader { // Xファイルの文字列データが一括して格納される private StringBuffer data; // Xファイル内の部分文字列の先頭を指すインデックス private int index; // メッシュとマテリアルの連想配列 private HashMap<String, Mesh> meshMap; private HashMap<String, Material> materialMap; // ポリゴンごとの頂点インデックス格納用 private List<List<Integer>> vertexIndices; // メッシュ private Mesh mesh; // ---------------------------------------- public Loader() { data = new StringBuffer(); vertexIndices = new ArrayList<List<Integer>>(); meshMap = new HashMap<String, Mesh>(); materialMap = new HashMap<String, Material>(); } // ---------------------------------------- // ファイル名を指定してメッシュを読み込み public Mesh loadX(final String fileName) { // 色々初期化 mesh = null; vertexIndices.clear(); meshMap.clear(); materialMap.clear(); index = 0; data.delete(0, data.length()); String[] lines = loadStrings(fileName); for(String line : lines) { // コメント除去の処理 data.append(line.replaceAll("(##|//).*", "") + "\n"); } List<PVector> textureCoordsList = null; while(index < data.length()) { String token = getNextToken(); // テンプレートは読み飛ばす if(token.equals("template")) skipBlock(); // ==================== // Material(前方宣言) // ==================== else if(token.equals("Material")) loadMaterialData(); // ==================== // MESH // ==================== else if(token.equals("Mesh")) mesh = loadMeshData(); // ==================== // MeshTextureCoords // ==================== else if(token.equals("MeshTextureCoords")) loadTextureCoords(); // ==================== // MeshMaterialList // ==================== else if(token.equals("MeshMaterialList")) loadMaterialListData(); } return mesh; } // ---------------------------------------- // { } で囲まれたブロックを読み飛ばす private void skipBlock() { try { // はじめの { まで読み飛ばす while(!(getNextToken().equals("{")) && index < data.length()) ; int n = 1; while(n > 0 && index < data.length()) { String token = getNextToken(); if(token.equals("{")) ++n; if(token.equals("}")) --n; } } catch (RuntimeException ex) { ex.printStackTrace(); } } // ---------------------------------------- // Meshブロックを読む private Mesh loadMeshData() { Mesh mesh = new Mesh(); String token = getNextToken(); // トークンが { でない場合 if(!token.equals("{")) { meshMap.put(token, mesh); getNextToken(); } // 頂点リストを作成 List<PVector> verticesList = new ArrayList<PVector>(); vertexIndices.clear(); // 全頂点数を取得 int nVertices = Integer.parseInt(getNextToken()); // 各頂点の座標を取得 for(int i = 0; i < nVertices; ++i) { float x = Float.parseFloat(getNextToken()); float y = Float.parseFloat(getNextToken()); float z = Float.parseFloat(getNextToken()); verticesList.add(new PVector(x, y, z)); } // ポリゴン数を取得 int nPolygons = Integer.parseInt(getNextToken()); for(int i = 0; i < nPolygons; ++i) { Polygon poly = new Polygon(); // ポリゴンごとに頂点インデックスリストを作る List<Integer> indices = new ArrayList<Integer>(); // 1ポリゴンあたりの頂点数 int nVerticesPerPolygon = Integer.parseInt(getNextToken()); for(int j = 0; j < nVerticesPerPolygon; ++j) { // 頂点インデックスを取得 int verticesIndex = Integer.parseInt(getNextToken()); indices.add(verticesIndex); // 頂点インデックスから該当する頂点を取得してポリゴンに追加 poly.addVertex(verticesList.get(verticesIndex)); } vertexIndices.add(indices); mesh.add(poly); } return mesh; } // ---------------------------------------- // Materialブロックを読む private Material loadMaterialData() { Material material = new Material(); String token = getNextToken(); // トークンが { でない場合 if(!token.equals("{")) { materialMap.put(token, material); getNextToken(); } int r = 0xFF & round(255 * Float.parseFloat(getNextToken())); int g = 0xFF & round(255 * Float.parseFloat(getNextToken())); int b = 0xFF & round(255 * Float.parseFloat(getNextToken())); int a = 0xFF & round(255 * Float.parseFloat(getNextToken())); material.faceColor = a << 24 | r << 16 | g << 8 | b; material.power = Float.parseFloat(getNextToken()); r = 0xFF & round(255 * Float.parseFloat(getNextToken())); g = 0xFF & round(255 * Float.parseFloat(getNextToken())); b = 0xFF & round(255 * Float.parseFloat(getNextToken())); material.specularColor = 0xFF << 24 | r << 16 | g << 8 | b; r = 0xFF & round(255 * Float.parseFloat(getNextToken())); g = 0xFF & round(255 * Float.parseFloat(getNextToken())); b = 0xFF & round(255 * Float.parseFloat(getNextToken())); material.emissiveColor = 0xFF << 24 | r << 16 | g << 8 | b; if(getNextToken().equals("TextureFilename")) { getNextToken(); // トークン「{」 String textureFileName = getNextToken(); material.texture = loadImage(textureFileName); } return material; } // ---------------------------------------- // MeshTextureCoordsブロックを読む private void loadTextureCoords() { getNextToken(); // 「{」 int nCoords = Integer.parseInt(getNextToken()); List<PVector> coordsList = new ArrayList<PVector>(); if(mesh == null) return; for(int i = 0; i < nCoords; ++i) { float u = Float.parseFloat(getNextToken()); float v = Float.parseFloat(getNextToken()); PVector texCoords = new PVector(u, v); coordsList.add(texCoords); } for(int i = 0; i < vertexIndices.size(); ++i) { Polygon poly = mesh.getPolygon(i); List<Integer> indices = vertexIndices.get(i); for(Integer index : indices) { poly.addTextureCoord(coordsList.get(index)); } } } // ---------------------------------------- // MeshMaterialListブロックを読む private void loadMaterialListData() { getNextToken(); // 「{」 int nMaterials = Integer.parseInt(getNextToken()); int nPolygons = Integer.parseInt(getNextToken()); // マテリアルインデックスリストを作成 List<Integer> materialIndicesList = new ArrayList<Integer>(); for(int i = 0; i < nPolygons; ++i) materialIndicesList.add(Integer.parseInt(getNextToken())); // Xで定義されているマテリアルを読み込み List<Material> materialList = new ArrayList<Material>(); for(int i = 0; i < nMaterials; ++i) { String token = getNextToken(); while(token.equals("{")) token = getNextToken(); while(token.equals("}")) token = getNextToken(); Material material; if(token.equals("Material")) material = loadMaterialData(); else material = materialMap.get(token); materialList.add(material); } for(int i = 0; i < nPolygons; ++i) { Polygon poly = mesh.getPolygon(i); int index = materialIndicesList.get(i); Material m = materialList.get(index); poly.setMaterial(m); } } // ---------------------------------------- // 次のトークンを得る private String getNextToken() { // 最初にインデックスの範囲チェック if(!(index < data.length())) return ""; // ファイルの末尾に達した場合 // デリミタ(区切り文字)の読み飛ばし String delimiters = " \t\r\n,;\""; while(index < data.length() && strChr(delimiters, data.charAt(index))) ++index; if(!(index < data.length())) return ""; // ファイルの末尾 // 中括弧の検出 char[] c = { data.charAt(index) }; if(c[0] == '{' || c[0] == '}') { ++index; return new String(c); } // それ以外のトークン検出 delimiters += "{}"; int startIndex = index; int endIndex = index; while(endIndex < data.length() && !strChr(delimiters, data.charAt(endIndex))) ++endIndex; index = endIndex; return data.substring(startIndex, endIndex); } // ---------------------------------------- // 文字列シーケンスsの中に、cが含まれているかどうかをチェック private boolean strChr(final CharSequence s, final char c) { for(int i = 0; i < s.length(); ++i) if(s.charAt(i) == c) return true; return false; } }
【使い方】
Mesh mesh; void setup() { size(640, 480, P3D); // ファイル名を指定してXをロード mesh = new Loader().loadX("3 テクスチャ付き.x"); } void draw() { background(0); camera(); lights(); noStroke(); camera(-500, 0, -500, 0, 0, 0, 0, -1, 0); scale(200); // メッシュの描画 mesh.render(); }
ちなみにエラー処理とかはあまりしていないので、X ファイルが規格外だったりテクスチャファイルが見つからなかったりすると平気で RuntimeException をぶん投げると思いますがそこはご愛嬌という事で。
【MESH GURUのサンプルに挑戦】
鎌田茂雄 著 『MESH GURU』 の第 8 章で使用されているサンプルのうち、アニメーションを含まない X ファイルをパースしてみる事にします。以下では、Processing における実行結果と、使用した X ファイルをそのまま掲載しています。
『MESH GURU』 のサンプルは Metasequoia が吐く X ファイルとはじゃっかん構造が違い、冒頭で名前つきの Material を定義して、以降は識別子で Material を参照する形になっています。なんとも意地の悪い書き方ではありますが、可能な限り対応する事にしました。
【世界最小Xファイル】
xof 0303txt 0032 ///////////////////メッシュ////////////////////////// Mesh Mesh_Triangle { //////頂点データ部//////////// 3; -1.0;-1.0;0.0;, -1.0;1.0;0.0;, 1.0;-1.0;0.0;; //////ポリゴンデータ部/////// 1; 3;0,1,2;; }
【マテリアル付き】
xof 0303txt 0032 //////////////////////////マテリアル///////////////////// Material Triangle_Blue { 0.0;0.0;1.0;1.0;; 51.2; 0.0;0.0;0.0;; 0.0;0.0;0.0;; } ///////////////////メッシュ////////////////////////// Mesh Mesh_Triangle { 3; -1.0;-1.0;0.0;, -1.0;1.0;0.0;, 1.0;-1.0;0.0;; 1; 3;0,1,2;; ///////////////////マテリアルリスト//////////// MeshMaterialList { 1; 1; 0; { Triangle_Blue } } }
【テクスチャ付き】
xof 0303txt 0032 //////////////////////////マテリアル///////////////////// Material Triangle_Tex { 1.0;1.0;1.0;1.0;; 51.2; 0.0;0.0;0.0;; 0.0;0.0;0.0;; TextureFilename { "コンクリート.bmp"; } } ///////////////////メッシュ////////////////////////// Mesh Mesh_Triangle { 3; -1.0;-1.0;0.0;, -1.0;1.0;0.0;, 1.0;-1.0;0.0;; 1; 3;0,1,2;; ///////////////////テクスチャ座標/////////////// MeshTextureCoords { 3; 0.0;1.0;, 0.0;0.0;, 1.0;1.0;; } ///////////////////マテリアルリスト//////////// MeshMaterialList { 1; 1; 0; { Triangle_Tex } } }
※ 文字コードが UTF-8 以外の場合、日本語のテクスチャを読み込もうとした瞬間に死にます。
【おまけ:魔道少女】
↓
【おまけその2:どせいさん】
こちらのサイトから拝借した「どせいさん」を表示してみました。なお、左右が反転しているのは、座標軸の取り方によるものです。
0 件のコメント:
コメントを投稿
ひとことどうぞφ(・ω・,,)