package org.libspark.pv3d { import flash.events.*; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.utils.ByteArray; import flash.utils.Dictionary; import org.papervision3d.core.geom.Face3D; import org.papervision3d.core.geom.Mesh3D; import org.papervision3d.core.geom.Vertex3D; import org.papervision3d.core.Matrix3D; import org.papervision3d.core.NumberUV; import org.papervision3d.core.proto.GeometryObject3D; import org.papervision3d.core.proto.MaterialObject3D; import org.papervision3d.events.FileLoadEvent; import org.papervision3d.materials.BitmapFileMaterial; import org.papervision3d.materials.ColorMaterial; import org.papervision3d.materials.MaterialsList; import org.papervision3d.objects.DisplayObject3D; /** * メタセコイアのファイル(.mqo)を読み込むためのクラス。 */ public class Metasequoia_1_5 extends Mesh3D { private var mLoader:URLLoader; private var mFilename:String; private var mMaterialsToLoad:int =0; private var mMaterialNames:Array; private var mScale:Number = 1; private var mCharset:String = "shift_jis"; /** * @param file 読み込むファイルの URL。絶対パスで指定してください。 * @param scale 読み込むときの拡大率。1 が原寸大です。 * @param charset ファイルの文字コード。よほどのことが無い限り shift_jis だと思います。 */ function Metasequoia_1_5(file:String, scale:Number = 1, charset:String = "shift_jis") { this.materials = new MaterialsList(); this.mCharset = charset; super(null, new Array(), new Array(), null); mFilename = file; mScale = scale; loadMetasequoia(); } private function loadMetasequoia():void { mLoader = new URLLoader(); mLoader.dataFormat = URLLoaderDataFormat.BINARY; mLoader.addEventListener(Event.COMPLETE, completeHandler); mLoader.addEventListener(IOErrorEvent.IO_ERROR, defaultHandler); mLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, defaultHandler); mLoader.addEventListener(ProgressEvent.PROGRESS, defaultHandler); mLoader.load(new URLRequest(mFilename)); } private function completeHandler(evt:Event):void { var byteArray:ByteArray = ByteArray(mLoader.data); buildMetasequoia(byteArray.readMultiByte(byteArray.length, mCharset)); dispatchEvent(evt.clone()); } private function defaultHandler(evt:Event):void { dispatchEvent(evt.clone()); } private function buildMetasequoia(plainText:String):void { var lines:Array = plainText.split("\r\n"); trace("num lines = " + lines.length); var l:int = 0; // Material チャンクを読み込む l = parseMaterialChunk(lines, 0); // Object チャンクを読み込めなくなるまで読み込む while (l != -1) { l = parseObjectChunk(lines, l); } geometry.ready = true; } /** * Material チャンクの開始行を返します。 * 見つからなかった場合には -1 を返します。 */ private function getMaterialChunkLine(lines:Array, startLine:int = 0):int { for (var i:uint = startLine; i < lines.length; ++i) { if (lines[i].indexOf("Material") == 0) { return int(i); } } return -1; } /** * Material チャンクを読み込み、その最後の行番号を返します。 * エラーが起こった場合は -1 を返します。 */ private function parseMaterialChunk(lines:Array, startLine:int):int { var l:int = getMaterialChunkLine(lines, startLine); if (l == -1) { return -1; } // 解析中の行の文字列 var line:String = lines[l]; // マテリアル数を取得 var num:Number = parseInt(line.substr(9)); if (isNaN(num)) { return -1; } ++l; mMaterialNames = new Array(); // } で閉じているところの行番号 var endLine:int = l + int(num); // mqo ファイルのあるディレクトリのパス var path:String = mFilename.slice(0, mFilename.lastIndexOf("/") + 1); for (; l < endLine; ++l) { var material:MaterialObject3D; line = lines[l]; // マテリアルの名前を取得 var nameBeginIndex:int = line.indexOf("\""); var nameEndIndex:int = line.indexOf("\"", nameBeginIndex + 1); var name:String = line.substring(nameBeginIndex + 1, nameEndIndex); mMaterialNames.push(name); // テクスチャファイル名 var tex:String = getParam(line, "tex"); if (tex) { // テクスチャファイル名を取り囲む " " を取り除く tex = tex.substr(1, tex.length - 2); mMaterialsToLoad++; // テクスチャの URL を絶対にして読み込む material = new BitmapFileMaterial(path + tex); material.addEventListener(FileLoadEvent.LOAD_COMPLETE, materialLoadCompleteHandler); material.addEventListener(FileLoadEvent.LOAD_ERROR, materialLoadErrorHandler); // あまり重さが変わらないのでせっかくだからスムージング material.smooth = true; } else { // 形式 - col(1.000 1.000 0.000 1.000) var colorstr:String = getParam(line, "col"); if (colorstr != null) { var color:Array = colorstr.match(/\d+\.\d+/g); var r:int = parseFloat(color[0]) * 255; var g:int = parseFloat(color[1]) * 255; var b:int = parseFloat(color[2]) * 255; var a:Number = parseFloat(color[3]) * 100; trace("rgb = " + r + "," + g + "," + b); material = new ColorMaterial((r << 16) | (g << 8) | b); } else { material = MaterialObject3D.DEFAULT; } } material.doubleSided = true; material.name = name; materials.addMaterial(material, name); } return endLine; } private function materialLoadCompleteHandler(evt:FileLoadEvent):void { mMaterialsToLoad--; if(mMaterialsToLoad == 0){ //COLLADA のソースにあった謎の一行。不具合の元になるのでコメントアウト //materials = new MaterialsList(); dispatchEvent(new FileLoadEvent(FileLoadEvent.COLLADA_MATERIALS_DONE)); } } private function materialLoadErrorHandler(evt:FileLoadEvent):void { mMaterialsToLoad--; if(mMaterialsToLoad == 0){ dispatchEvent(new FileLoadEvent(FileLoadEvent.COLLADA_MATERIALS_DONE)); } } /** * Object チャンクの開始行を返します。 * 見つからなかった場合には -1 を返します。 */ private function getObjectChunkLine(lines:Array, startLine:int = 0):int { for (var i:uint = startLine; i < lines.length; ++i) { if (lines[i].indexOf("Object") == 0) { return int(i); } } return -1; } /** * Object チャンクを読み込み、その最後の行番号を返します。 * エラーが起こった場合は -1 を返します。 */ private function parseObjectChunk(lines:Array, startLine:int):int { var vertices:Array = geometry.vertices; var faces:Array = geometry.faces; var l:int = getObjectChunkLine(lines, startLine); if (l == -1) { return -1; } // 解析中の行の文字列 var line:String = lines[l]; // オブジェクト名を取得 var objectName:String = line.substring(8, line.indexOf("\"", 8)); ++l; // vertex チャンクを検索 var vline:int = getChunkLine(lines, "vertex", l); if (vline == -1) { return -1; } // プロパティを読み込む var properties:Dictionary = new Dictionary(); for (; l < vline; ++l) { line = lines[l]; var props:Array = RegExp(/^\s*([\w]+)\s+(.*)$/).exec(line); properties[props[1]] = props[2]; } line = lines[l]; l = vline + 1; // 頂点数を取得 var numVertices:int = parseInt(line.substring(line.indexOf("vertex") + 7)); var vertexEndLine:int = l + numVertices; var firstVertexIndex:int = vertices.length; // vertex チャンクを読み込む for (; l < vertexEndLine; ++l) { line = lines[l]; var coords:Array = line.match(/(-?\d+\.\d+)/g); var x:Number = parseFloat(coords[0]) * mScale; var y:Number = parseFloat(coords[1]) * mScale; var z:Number = parseFloat(coords[2]) * mScale; vertices.push(new Vertex3D(x, y, z)); } // face チャンクを検索 l = getChunkLine(lines, "face", l); if (l == -1) { return -1; } line = lines[l++]; // 面数を取得 var numFaces:int = parseInt(line.substring(line.indexOf("face") + 5)); var faceEndLine:int = l + numFaces; // face チャンクを読み込む for (; l < faceEndLine; ++l) { if (properties["visible"] == "15") { parseFace(faces, lines[l], vertices, firstVertexIndex, properties); } } return l; } private function parseFace(faces:Array, line:String, vertices:Array, vertexOffset:int, properties:Dictionary):void { var vstr:String = getParam(line, "V"); var mstr:String = getParam(line, "M"); var uvstr:String = getParam(line, "UV"); var v:Array = (vstr != null) ? vstr.match(/\d+/g) : []; var uv:Array = (uvstr != null) ? uvstr.match(/-?\d+\.\d+/g) : []; var a:Vertex3D; var b:Vertex3D; var c:Vertex3D; var d:Vertex3D; var materialName:String; var uvA:NumberUV; var uvB:NumberUV; var uvC:NumberUV; var uvD:NumberUV; var face:Face3D; if (v.length == 3) { a = vertices[parseInt(v[0]) + vertexOffset]; b = vertices[parseInt(v[1]) + vertexOffset]; c = vertices[parseInt(v[2]) + vertexOffset]; if (mstr != null) { materialName = mMaterialNames[parseInt(mstr)]; } if (uv.length != 0) { uvA = new NumberUV(parseFloat(uv[0]), 1 - parseFloat(uv[1])); uvB = new NumberUV(parseFloat(uv[2]), 1 - parseFloat(uv[3])); uvC = new NumberUV(parseFloat(uv[4]), 1 - parseFloat(uv[5])); face = new Face3D([a, b, c], materialName, [uvA, uvB, uvC]); } else { face = new Face3D([a, b, c], materialName, [new NumberUV(0, 0), new NumberUV(1, 0), new NumberUV(0, 1)]); } faces.push(face); if (properties["mirror"] == "1") { var mirrorAxis:int = parseInt(properties["mirror_axis"]); a = mirrorVertex(a, mirrorAxis); b = mirrorVertex(b, mirrorAxis); c = mirrorVertex(c, mirrorAxis); vertices.push(a); vertices.push(b); vertices.push(c); face = new Face3D([a, b, c], face.materialName, face.uv); faces.push(face); } } else if (v.length == 4) { a = vertices[parseInt(v[0]) + vertexOffset]; b = vertices[parseInt(v[1]) + vertexOffset]; c = vertices[parseInt(v[2]) + vertexOffset]; d = vertices[parseInt(v[3]) + vertexOffset]; if (mstr != null) { materialName = mMaterialNames[parseInt(mstr)]; } if (uv.length != 0) { uvA = new NumberUV(parseFloat(uv[0]), 1 - parseFloat(uv[1])); uvB = new NumberUV(parseFloat(uv[2]), 1 - parseFloat(uv[3])); uvC = new NumberUV(parseFloat(uv[4]), 1 - parseFloat(uv[5])); uvD = new NumberUV(parseFloat(uv[6]), 1 - parseFloat(uv[7])); } else { uvA = new NumberUV(0, 0); uvB = new NumberUV(1, 0); uvC = new NumberUV(0, 1); uvD = new NumberUV(1, 1); } face = new Face3D([a, b, c], materialName, [uvA, uvB, uvC]); faces.push(face); face = new Face3D([c, d, a], materialName, [uvC, uvD, uvA]); faces.push(face); if (properties["mirror"] == "1") { mirrorAxis = parseInt(properties["mirror_axis"]); a = mirrorVertex(a, mirrorAxis); b = mirrorVertex(b, mirrorAxis); c = mirrorVertex(c, mirrorAxis); d = mirrorVertex(d, mirrorAxis); vertices.push(a); vertices.push(b); vertices.push(c); vertices.push(d); face = new Face3D([a, b, c], materialName, [uvA, uvB, uvC]); faces.push(face); face = new Face3D([c, d, a], materialName, [uvC, uvD, uvA]); faces.push(face); } } } /** * 頂点を軸に沿って反転させたものを返します。 */ private static function mirrorVertex(v:Vertex3D, axis:int):Vertex3D { return new Vertex3D( ((axis & 1) != 0) ? -v.x : v.x, ((axis & 2) != 0) ? -v.y : v.y, ((axis & 4) != 0) ? -v.z : v.z); } /** * Object チャンクの開始行を返します。 */ private static function getChunkLine(lines:Array, chunkName:String, startLine:int = 0):int { for (var i:uint = startLine; i < lines.length; ++i) { if (lines[i].indexOf(chunkName) != -1) { return int(i); } } return -1; } /** * line 内で paramName(...) という形式で指定されているパラメータを返します。 */ private static function getParam(line:String, paramName:String):String { var prefix:String = paramName + "("; var prefixLen:int = prefix.length; var begin:int = line.indexOf(prefix, 0); if (begin == -1) { return null; } var end:int = line.indexOf(")", begin + prefixLen); if (end == -1){ return null; } return line.substring(begin + prefixLen, end); } } }