forked from: pv3dpreviewer on wonderfl(+mqo)

by _perfect forked from pv3dpreviewer on wonderfl(+mqo) (diff: 947)
Flashでローカルの3Dモデルを読み込んで表示できたらいいなー
ということでいろいろ活用して作りました。

モデルファイルとして dae 3ds md2 kmz mqo に、
テクスチャは jpg png gif に対応しています。
daeはアニメーションを含んでいると適当に再生できます(後述)。
・使い方
右クリックメニューからLoad Meshでモデルファイルを読み込みます。
Flashのローカルファイルの扱いの使用のためテクスチャを自動で読み込みません。

右クリックメニューからLoad Textureでテクスチャを読み込みます。
最後に読み込んだモデルに対してテクスチャを割り当てます。
ただし、複数のテクスチャを組み合わせることはできません。
#考え付く方法としては、マテリアルごとにLoadTexutureをコンテキストに挿入して
#マテリアルごとに読み込ませていくとか?
#だれかforkして作ってください(ぇ

・操作方法
本元のページにも書かれていますが、改めて
右クリック: メニューを開きます
マウスホイール: ズームイン・アウト 
F: 前からの視点
A: 後からの視点
L: 左からの視点
R: 右からの視点
T: 上からの視点
B: 下からの視点
P: パースをつけた視点(マウスで回転
UP / DOWN: モデルの拡大縮小
モデルをダブルクリック: アニメーションを再生
#アニメーションはとりあえず補間なしでキーフレームでかくかく動きます。
#毎フレームの変化を出力するようにしておけば滑らかになるに違いありません

・謝辞
元となっているpv3dpreviewerの開発者mrdoob氏、
そしてMetasequoiaクラスの製作者rch850氏に感謝いたします。

大きさとか割と適当なのでそこは用改善かもです。

mrdoob's pv3dpreviewer (rev.144)
http://code.google.com/p/mrdoob/wiki/pv3dpreviewer

Metasequoia
http://www.libspark.org/wiki/rch850/Metasequoia

Papervision3D Previewer

Released under MIT license:
http://www.opensourc
♥0 | Line 705 | Modified 2010-10-13 21:38:36 | All rights reserved
play

ActionScript3 source code

// forked from nanmo's pv3dpreviewer on wonderfl(+mqo)
//Flashでローカルの3Dモデルを読み込んで表示できたらいいなー
//ということでいろいろ活用して作りました。
//
//モデルファイルとして dae 3ds md2 kmz mqo に、
//テクスチャは jpg png gif に対応しています。
//daeはアニメーションを含んでいると適当に再生できます(後述)。

//・使い方
//右クリックメニューからLoad Meshでモデルファイルを読み込みます。
//Flashのローカルファイルの扱いの使用のためテクスチャを自動で読み込みません。
//
//右クリックメニューからLoad Textureでテクスチャを読み込みます。
//最後に読み込んだモデルに対してテクスチャを割り当てます。
//ただし、複数のテクスチャを組み合わせることはできません。
//#考え付く方法としては、マテリアルごとにLoadTexutureをコンテキストに挿入して
//#マテリアルごとに読み込ませていくとか?
//#だれかforkして作ってください(ぇ
//
//・操作方法
//本元のページにも書かれていますが、改めて
//右クリック: メニューを開きます
//マウスホイール: ズームイン・アウト 
//F: 前からの視点
//A: 後からの視点
//L: 左からの視点
//R: 右からの視点
//T: 上からの視点
//B: 下からの視点
//P: パースをつけた視点(マウスで回転
//UP / DOWN: モデルの拡大縮小
//モデルをダブルクリック: アニメーションを再生
//#アニメーションはとりあえず補間なしでキーフレームでかくかく動きます。
//#毎フレームの変化を出力するようにしておけば滑らかになるに違いありません
//
//・謝辞
//元となっているpv3dpreviewerの開発者mrdoob氏、
//そしてMetasequoiaクラスの製作者rch850氏に感謝いたします。
//
//大きさとか割と適当なのでそこは用改善かもです。
//
//
//mrdoob's pv3dpreviewer (rev.144)
//http://code.google.com/p/mrdoob/wiki/pv3dpreviewer
//
//Metasequoia
//http://www.libspark.org/wiki/rch850/Metasequoia
//
/**
 * Papervision3D Previewer
 *
 * Released under MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 * 
 *  09.02.01        1.3        Trent Grover    + New camera system
 *                                          + Additional supported model types
 *                                          + Animation playback
 *                                          + Added model scale change using up and down arrow keys
 *                                          + Mouse click selects current mesh for scale change or animation playback
 *                          Mr.doob            + General code clean up
 *    08.02.22        1.2        Mr.doob            + Refactoring (in other words, re-coded from scratch)
 *                                            + Using Papervision3D 2.0
 *    07.06.13        1.1        Mr.doob            + MacOS support
 *                                            + Triangles rendered stats added on the FPS bar
 *                                            + Object loaded now rotates automatically
 *                                            + Camera behaviour amended 
 *     07.04.27        1.0        Mr.doob            + Big bang
 *
 **/

package
{
        import flash.utils.ByteArray;
        import flash.display.Loader;
        import flash.display.Bitmap;
        import org.papervision3d.materials.utils.MaterialsList

    import net.hires.debug.Stats;

    import org.papervision3d.cameras.*;
    import org.papervision3d.core.proto.MaterialObject3D;
    import org.papervision3d.events.*;
    import org.papervision3d.materials.*;
    import org.papervision3d.materials.special.*;
    import org.papervision3d.objects.*;
    import org.papervision3d.objects.parsers.*;
    import org.papervision3d.render.BasicRenderEngine;
    import org.papervision3d.scenes.Scene3D;
    import org.papervision3d.view.Viewport3D;

    import flash.display.Sprite;
    import flash.events.ContextMenuEvent;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.net.FileFilter;
    import flash.net.FileReference;
    import flash.ui.ContextMenuItem;    

    [SWF(width="800",height="600",backgroundColor="#101010")]

    public class Main extends Sprite
    {
        public var fileRef : FileReference;
                public var textureLoader : Loader;

        public var camera : Camera3D;
        public var scene : Scene3D;
        public var renderer : BasicRenderEngine;
        public var viewport : Viewport3D;

        public var mesh : DisplayObject3D;

        public var CAMERA_VIEW_TOP : String = "top";
        public var CAMERA_VIEW_BOTTOM : String = "bottom";
        public var CAMERA_VIEW_LEFT : String = "left";
        public var CAMERA_VIEW_RIGHT : String = "right";
        public var CAMERA_VIEW_FRONT : String = "front";
        public var CAMERA_VIEW_BACK : String = "back";
        public var CAMERA_VIEW_PERS : String = "pers";
        public var CAMERA_VIEW : String = CAMERA_VIEW_PERS;

        public function Main()
        {
            Config.setup(this);
            init();
            addChild(new Stats());
        }

        public function init() : void
        {
            fileRef = new FileReference();
            fileRef.addEventListener(Event.SELECT, this.onFileSelect);

            init3D();

            stage.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
            stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
            addEventListener(Event.ENTER_FRAME, loop);    
        }

        public function init3D() : void
        {
            scene = new Scene3D();
            renderer = new BasicRenderEngine();
            viewport = new Viewport3D(800, 600, true, true);
            addChild(viewport);

            camera = new Camera3D();
            camera.zoom = 100;
            camera.x = 0;
            camera.z = -1500;
            camera.y = 0;

            scene.addChild(new PlaneGrid3D());

            /*
            // create a test cube primitive
            var materials:MaterialsList = new MaterialsList(
            {
            front:  new ColorMaterial(0xFFFFFF),
            back:   new ColorMaterial(0xFF0000),
            right:  new ColorMaterial(0x00FF00),
            left:   new ColorMaterial(0x333333),
            top:    new ColorMaterial(0x0000FF),
            bottom: new ColorMaterial(0xFFFF00)
            });
            scene.addChild(new Cube(materials, 300, 300, 300));
             */

            // Context Menu
            var cmiLoad : ContextMenuItem = new ContextMenuItem("Load Mesh", true);
            cmiLoad.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, openFileSelector);
            Config.cm.customItems.push(cmiLoad);
                        
                        // texute load context menu
                        var cmiTextureLoad : ContextMenuItem = new ContextMenuItem("Load Texture", true);
                        cmiTextureLoad.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, openTextureSelector);
                        Config.cm.customItems.push(cmiTextureLoad);
        }

        
        // .. LOOP ....................................................................................

        public function loop(e : Event) : void
        {
            handleCamera();
            renderer.renderScene(scene, camera, viewport);
        }

        public function handleCamera() : void
        {
            if (CAMERA_VIEW == CAMERA_VIEW_PERS)
            {
                // if pitch == 0, everything disappears?
                var pitch : Number = (-mouseY * 0.2);
                if (pitch >= 0)
                    pitch = -0.1;
                camera.orbit(pitch, mouseX * 0.2);
            }
        }

        
        // .. USER INPUT ..............................................................................

        public function onKeyboardEvent(e : KeyboardEvent) : void
        {
            switch(e.type)
            {
                case KeyboardEvent.KEY_UP:

                    switch(e.keyCode)
                    {
                        // swap viewpoints
                        case String("T").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_TOP;        
                            camera.orbit(-0.1, 270);        
                            break;
                        case String("B").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_BOTTOM;    
                            camera.orbit(-179.9, 270);    
                            break;
                        case String("L").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_LEFT;        
                            camera.orbit(-90, 0);        
                            break;
                        case String("R").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_RIGHT;    
                            camera.orbit(-90, 180);        
                            break;
                        case String("F").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_FRONT;    
                            camera.orbit(-90, 270);        
                            break;
                        case String("A").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_BACK;        
                            camera.orbit(-90, 90);        
                            break;
                        case String("P").charCodeAt():    
                            CAMERA_VIEW = CAMERA_VIEW_PERS;                                    
                            break;
                        case 38:    
                            mesh.scale += 1;        
                            break;
                        case 40:    
                            if (mesh.scale > 1)
                                        mesh.scale -= 1;
                            break;
                    }

                    break;
            }
        }

        public function onMouseEvent(e : MouseEvent) : void
        {
            switch(e.type)
            {
                case MouseEvent.MOUSE_WHEEL:    
                    camera.zoom += e.delta;        
                    break;
            }
        }

        
        // .. FILE HANDLING ...........................................................................

        public function openFileSelector(e : Event = null) : void
        {
            var fileTypes : Array = new Array(new FileFilter("3D Files (*.mqo, *.dae, *.3ds, *.md2, *.kmz)", "*.mqo; *.dae; *.3ds; *.md2; *.kmz"));

            try
            {
                fileRef.browse(fileTypes) as Boolean;
            }
            catch (error : Error)
            {
                trace("Unable to browse for files.");
            }
        }

        public function openTextureSelector(e : Event = null) : void
        {
            var fileTypes : Array = new Array(new FileFilter("Bitmap Files (*.jpg, *.png, *.gif)", "*.jpg; *.png; *.gif"));

            try
            {
                fileRef.browse(fileTypes) as Boolean;
            }
            catch (error : Error)
            {
                trace("Unable to browse for files.");
                                //handleMesh();
            }
        }

//begin changes
        public function onFileSelect(e : Event) : void
        {
            //var file : FileReference = FileReference(e.target);
            //var file_type : String = FileUtils.getFileType(file.name).toLowerCase();

                        fileRef.addEventListener(Event.COMPLETE,loadFileComplete);
                        fileRef.load();
        }

                public function loadFileComplete( e: Event ):void
                {
                        var file_type : String = FileUtils.getFileType(fileRef.name).toLowerCase();
            switch(file_type)
            {
                case ".jpg":
                case ".png":
                case ".gif":
                    loadTexture(fileRef.data);
                    break;
                case ".mqo":
                case ".dae":
                case ".3ds":
                case ".md2":
                case ".kmz":
                    parseMesh(fileRef.data, file_type);
                    break;
            }
                        removeEventListener(Event.COMPLETE,loadFileComplete);
                }

                public function parseMesh(file_data : ByteArray, file_extension : String = null) :void
                {
                         if (!file_extension)
                file_extension = FileUtils.getFileType(fileRef.name);

            switch(file_extension)
            {
                case ".ase":
                    //mesh = new Ase(new ColorMaterial(Math.random() * 0xffffff), file_name, .1);
                    //mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    break;
                case ".mqo":
                    mesh = new Metasequoia();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);

                                        (mesh as Metasequoia).parse(file_data);
                    break;

                case ".dae":

                    mesh = new DAE();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    mesh.addEventListener(FileLoadEvent.ANIMATIONS_COMPLETE, handleDAEanim);
                                        
                    DAE(mesh).load(file_data);
                    break;

                case ".3ds":

                    mesh = new Max3DS();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    Max3DS(mesh).load(file_data);
                    break;

                case ".md2":
                    mesh = new MD2();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMD2);
                    MD2(mesh).load(file_data);
                    break;

                case ".kmz":

                    mesh = new KMZ();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    KMZ(mesh).load(file_data);
                    break;
            }   
                }
                public function loadTexture(file_data : ByteArray) :void
                {
                        textureLoader = new Loader();
                        textureLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, assignTexture);
                        textureLoader.loadBytes(file_data);
                }
                public function assignTexture( e :Event ) :void
                {
                        addHighlight(mesh, new BitmapMaterial( Bitmap(e.target.content).bitmapData ) );
                } 
//end changes
/*
 * begin unused area
        public function loadMesh(file_name : String, file_extension : String = null) : void
        {
            if (!file_extension)
                file_extension = FileUtils.getFileType(file_name);

            switch(file_extension)
            {
                case ".ase":

                    mesh = new Ase(new ColorMaterial(Math.random() * 0xffffff), file_name, .1);
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    break;

                case ".dae":

                    mesh = new DAE();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    mesh.addEventListener(FileLoadEvent.ANIMATIONS_COMPLETE, handleDAEanim);
                                        
                    DAE(mesh).load(file_name);
                    break;

                case ".3ds":

                    mesh = new Max3DS();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    Max3DS(mesh).load(file_name);
                    break;

                case ".md2":

                    mesh = new MD2();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMD2);
                    MD2(mesh).load(file_name);
                    break;

                case ".kmz":

                    mesh = new KMZ();
                    mesh.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleMesh);
                    KMZ(mesh).load(file_name);
                    break;
            }
        }

        public function loadTexture(file_name : String) : void
        {
            mesh.material = new BitmapFileMaterial(file_name);
            mesh.material.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleTexture);
        }

        public function handleTexture(e : FileLoadEvent) : void
        {
            addHighlight(mesh);
        }
 * end unused area
*/

        public function handleMD2(e : FileLoadEvent) : void
        {
            openTextureSelector();
            mesh.rotationZ = mesh.rotationY = 90;
            scene.addChild(mesh);
        }

// begin changes
        public function handleMesh(e : FileLoadEvent) : void
        {
            addHighlight(mesh);
                scene.addChild(mesh);
        }
// end changes

        public function handleDAEanim(e : Event) : void
        {
            DAE(mesh).play();
        }

// begin changes
        public function addHighlight(object : DisplayObject3D, material : MaterialObject3D = null) : void
        {
            if (object.children)
                for each(var child : DisplayObject3D in object.children)
                    addHighlight(child,material);

            if (!object.material)        return;
            if (!object.geometry.faces[0])    return;

            var original : MaterialObject3D;

            // This is a workaround for a bug. object.material should have the updated material
            // but for some reason its stuck on WireframeMaterial
            if ( material != null )
                        {
                            original = material;
                        }
                        else {
                            if (object.geometry.faces[0].material)
                original = object.geometry.faces[0].material;
                else
                original = object.material;
                        }

            var compositeMaterial : CompositeMaterial = new CompositeMaterial();
            compositeMaterial.addMaterial(original);
            compositeMaterial.addMaterial(new ColorMaterial(0x00ff00, 0));
            compositeMaterial.interactive = true;
            object.material = compositeMaterial;

            object.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, onInteractiveEvent);
            object.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, onInteractiveEvent);
            object.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onInteractiveEvent);
        }
// end changes
        
        // .. 3D LISTENERS

        public function onInteractiveEvent(e : InteractiveScene3DEvent) : void
        {
            switch(e.type)
            {
                case InteractiveScene3DEvent.OBJECT_OVER:

                    // turn on the highlight
                    CompositeMaterial(e.displayObject3D.material).materials[1].fillAlpha = 0.5;
                    break;

                case InteractiveScene3DEvent.OBJECT_OUT:

                    // turn off the highlight
                    CompositeMaterial(e.displayObject3D.material).materials[1].fillAlpha = 0;
                    break;

                case InteractiveScene3DEvent.OBJECT_CLICK:

                    // activate the selected mesh (for scale commands, etc)
                    // (must first traverse to the parent Ase or DAE)
                    var target : DisplayObject3D = e.displayObject3D;
                    
                    while (!((target is DAE) || (target is Ase) || (target is Max3DS) || (target is MD2) || (target is KMZ)) && (target.parent != null))
                        target = DisplayObject3D(target.parent);
                        
                    if ((target is DAE) || (target is Ase) || (target is Max3DS) || (target is MD2) || (target is KMZ))
                        mesh = target;

                    // trigger DAE animation
                    if (mesh is DAE)
                        DAE(mesh).play();
                    
                    break;
            }
        }
    }
}

//Config class
    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.display.StageAlign;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.events.ContextMenuEvent;
    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.net.navigateToURL;
    import flash.ui.ContextMenu;
    import flash.ui.ContextMenuItem;    

    class Config
    {
        public static var cm : ContextMenu;
        private static var projectName : String = "PV3D Previewer";
        private static var projectVer : String = "1.3";
        private static var projectURL : String = "http://code.google.com/p/mrdoob/";

        public static function setup( target : Sprite ) : void
        {
            // CONFIG STAGE
            target.stage.align = StageAlign.TOP_LEFT;
            target.stage.scaleMode = StageScaleMode.NO_SCALE;
            target.stage.quality = StageQuality.MEDIUM;
            
            // CONTEXT MENU SETUP
            cm = new ContextMenu();
            cm.hideBuiltInItems();
            target.contextMenu = cm;
            
            // CONTEXT MENU: INFO
            var cmiInfo : ContextMenuItem = new ContextMenuItem(projectName + " " + projectVer);
            cmiInfo.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, function(e : Event) : void 
            { 
                navigateToURL(new URLRequest(projectURL), "_blank"); 
            });
            cm.customItems.push(cmiInfo);
        }
    }

//FileUtils class
    class FileUtils
    {
        public static function getFileType(_file : String) : String
        {
            var file : Array = _file.split('.');
            return '.' + file[file.length - 1];
        }
    }

//PlaneGrid3D class
    import org.papervision3d.core.geom.Lines3D;
    import org.papervision3d.core.geom.renderables.Line3D;
    import org.papervision3d.core.geom.renderables.Vertex3D;
    import org.papervision3d.materials.special.LineMaterial;    

    class PlaneGrid3D extends Lines3D
    {
        public function PlaneGrid3D() : void
        {
            var i : int = 0;
            var xx : Number = 0;
            var yy : Number = 0;
            var dist : Number = 80;
            
            var v1 : Vertex3D;
            var v2 : Vertex3D;
            var l : Line3D;
            var lm : LineMaterial = new LineMaterial(0xFFFFFF, .5);
            
            super(lm);
            
            for (i = 0;i < 11; i++)
            {
                v1 = new Vertex3D(i * dist, 0, 0);
                v2 = new Vertex3D(i * dist, 0, 10 * dist);
                l = new Line3D(this, lm, 1, v1, v2);
                addLine(l);
            }
            
            for (i = 0;i < 11; i++)
            {
                v1 = new Vertex3D(0, 0, i * dist);
                v2 = new Vertex3D(10 * dist, 0, i * dist);
                l = new Line3D(this, lm, 1, v1, v2);
                addLine(l);
            }
            
            x = -400;
            z = -400;
        }        
    }

//below Metasequoia.as
/**
 * Metasequoia.as
 * 
 * @see http://snippets.libspark.org/
 * @see http://snippets.libspark.org/trac/wiki/rch850/Metasequoia
 * 
 * Copyright (c) 2007-2008 rch850
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

//package org.libspark.pv3d {
    import flash.display.BitmapData;
    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.libspark.pv3d.decoders.TGADecoder;
    import org.papervision3d.core.geom.TriangleMesh3D;
    import org.papervision3d.core.geom.renderables.Triangle3D;
    import org.papervision3d.core.geom.renderables.Vertex3D;
    import org.papervision3d.core.math.Matrix3D;
    import org.papervision3d.core.math.NumberUV;
    import org.papervision3d.core.proto.DisplayObjectContainer3D;
    import org.papervision3d.core.proto.GeometryObject3D;
    import org.papervision3d.core.proto.MaterialObject3D;
    import org.papervision3d.events.FileLoadEvent;
    import org.papervision3d.events.InteractiveScene3DEvent;
    import org.papervision3d.materials.BitmapFileMaterial;
    import org.papervision3d.materials.BitmapMaterial;
    import org.papervision3d.materials.ColorMaterial;
    import org.papervision3d.materials.utils.MaterialsList;
    import org.papervision3d.objects.DisplayObject3D;

    //import com.voidelement.images.BMPDecoder;    
    /**
    * メタセコイアのファイル(.mqo)を読み込むためのクラス。
    * 
    * var mqo = new Metasequoia();
    * mqo.addEventListener(...);
    * mqo.load("hoge.mqo");
    */
    //public class Metasequoia extends TriangleMesh3D {
        class Metasequoia extends TriangleMesh3D {
        /**
        * コンストラクタ
        */
        function Metasequoia() {
            this.materials = new MaterialsList();
            super(null, new Array(), new Array(), null);
        }
        
        /**
        * @param file 読み込むファイルの URL。絶対パスで指定してください。
        * @param scale 読み込むときの拡大率。1 が原寸大です。
        */
        public function load(file:String, scale:Number = 1):void {
            _filename = file;
            _scale = scale;
            loadMetasequoia();
        }
        

                /**
                読み込み済みのデータからパースするよ
                */
                public function parse(byteArray:ByteArray):void
                {
            buildMetasequoia(byteArray.readMultiByte(byteArray.length, charset));
            dispatchEvent(new FileLoadEvent(FileLoadEvent.LOAD_COMPLETE));
                }

        /**
         * ファイルの文字コード。よほどのことが無い限り shift_jis だと思います。
         */
        public var charset:String = "shift_jis";
        
        /**
         * 面の両側にマテリアルを貼るかどうかを指定します。
         */
        public var doubleSided:Boolean = false;
        
        /**
         * インタラクティビティを設定します。
         */
        public var interactive:Boolean = true;
        
        private var _loader:URLLoader;
        private var _filename:String;
        private var _materialsToLoad:int =0;
        private var _materialNames:Array;
        private var _scale:Number = 1;
        private var _prevMesh:DisplayObject3D;
        private var _prevDepth:int;
        
        private function loadMetasequoia():void {
            _loader = new URLLoader();
            _loader.dataFormat = URLLoaderDataFormat.BINARY;
            _loader.addEventListener(Event.COMPLETE, completeHandler);
            _loader.addEventListener(IOErrorEvent.IO_ERROR, defaultHandler);
            _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, defaultHandler);
            _loader.addEventListener(ProgressEvent.PROGRESS, defaultHandler);
            _loader.load(new URLRequest(_filename));
        }
        
        private function completeHandler(evt:Event):void {
            var byteArray:ByteArray = ByteArray(_loader.data);
            buildMetasequoia(byteArray.readMultiByte(byteArray.length, charset));
            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);
            
            _prevDepth = 0;
            _prevMesh = this;
            
            // 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;
            _materialNames = new Array();
            
            // } で閉じているところの行番号
            var endLine:int = l + int(num);
            
            // mqo ファイルのあるディレクトリのパス
            //var path:String = _filename.slice(0, _filename.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);
                _materialNames.push(name);
                
                // テクスチャファイル名
                //var tex:String = getParam(line, "tex");
                                /* どうせ読み込めないから無効化しとく
                var tex:String = "";
                if (tex) {
                    // テクスチャファイル名を取り囲む " " を取り除く
                                        tex = tex.substr(1, tex.length - 2);
                    _materialsToLoad++;
                    if (tex.toLowerCase().search(/\.tga$/) != -1) {
                        material = loadTGAMaterial(path + tex);
                    } else if (tex.toLowerCase().search(/\.bmp$/) != -1) {
                        material = loadBMPMaterial(path + tex);
                    } else {
                        // テクスチャの 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 = this.doubleSided;
                material.interactive = this.interactive;
                material.name = name;
                
                materials.addMaterial(material, name);
            }
            
            return endLine;
        }
        
        /**
         * Creates a BitmapMaterial from TGA file and returns it.
         */
        /*
                private function loadTGAMaterial(url:String):BitmapMaterial {
            var material:BitmapMaterial = new BitmapMaterial();
            var loader:URLLoader = new URLLoader();
            loader.dataFormat = URLLoaderDataFormat.BINARY;
            loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                var tga:TGADecoder = new TGADecoder(loader.data);
                material.bitmap = tga.bitmap;
                material.maxU = material.maxV = 1;
                material.resetMapping();
            });
            loader.load(new URLRequest(url));
            return material;
        }
        
        private function loadBMPMaterial(url:String):BitmapMaterial
        {
            var material:BitmapMaterial = new BitmapMaterial();
            var loader:URLLoader = new URLLoader();
            loader.dataFormat = URLLoaderDataFormat.BINARY;
            loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                var bmpLoader:URLLoader = event.target as URLLoader;
                var decoder:BMPDecoder = new BMPDecoder();
                material.bitmap = decoder.decode(bmpLoader.data);
                material.maxU = material.maxV = 1;
                material.resetMapping();
            });
            loader.load(new URLRequest(url));
            return material;
        }
        */
        private function materialLoadCompleteHandler(evt:FileLoadEvent):void {
            _materialsToLoad--;
            if(_materialsToLoad == 0){
                //COLLADA のソースにあった謎の一行。不具合の元になるのでコメントアウト
                //materials = new MaterialsList();
                dispatchEvent(new FileLoadEvent(FileLoadEvent.COLLADA_MATERIALS_DONE));
            }
        }
        
        private function materialLoadErrorHandler(evt:FileLoadEvent):void {
            _materialsToLoad--;
            if(_materialsToLoad == 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 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;
            
            var mesh:TriangleMesh3D = new TriangleMesh3D(null, new Array(), new Array(), objectName);
            var vertices:Array = mesh.geometry.vertices;
            var faces:Array = mesh.geometry.faces;
            
            // 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]) * _scale;
                var y:Number = parseFloat(coords[1]) * _scale;
                var z:Number = -parseFloat(coords[2]) * _scale;
                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);
                }
            }
            
            // Resolve parent-child relationship.
            var depth:int;
            try {
                depth = parseInt(properties["depth"]);
            } catch (e:Error) {
                depth = 0;
            }
            var parentMesh:DisplayObjectContainer3D = _prevMesh;
            if (depth <= 0) {
                parentMesh = this;
                depth = 0;
            } else {
                while (depth <= _prevDepth) {
                    parentMesh = DisplayObject3D(parentMesh).parent;
                    --_prevDepth;
                }
            }
            parentMesh.addChild(mesh);
            _prevMesh = mesh;
            _prevDepth = depth;
            
            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 material:MaterialObject3D;
            var uvA:NumberUV;
            var uvB:NumberUV;
            var uvC:NumberUV;
            var uvD:NumberUV;
            var face:Triangle3D;
            
            if (v.length == 3) {
                c = vertices[parseInt(v[0]) + vertexOffset];
                b = vertices[parseInt(v[1]) + vertexOffset];
                a = vertices[parseInt(v[2]) + vertexOffset];
                
                if (mstr != null) {
                    material = materials.getMaterialByName(_materialNames[parseInt(mstr)]);
                }
                
                if (uv.length != 0) {
                    uvC = new NumberUV(parseFloat(uv[0]), 1 - parseFloat(uv[1]));
                    uvB = new NumberUV(parseFloat(uv[2]), 1 - parseFloat(uv[3]));
                    uvA = new NumberUV(parseFloat(uv[4]), 1 - parseFloat(uv[5]));
                    face = new Triangle3D(this, [a, b, c], material, [uvA, uvB, uvC]);
                } else {
                    face = new Triangle3D(this, [a, b, c], material,
                        [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 Triangle3D(this, [c, b, a], material, face.uv.reverse());
                    faces.push(face);
                }
            } else if (v.length == 4) {
                d = vertices[parseInt(v[0]) + vertexOffset];
                c = vertices[parseInt(v[1]) + vertexOffset];
                b = vertices[parseInt(v[2]) + vertexOffset];
                a = vertices[parseInt(v[3]) + vertexOffset];
                
                if (mstr != null) {
                    material = materials.getMaterialByName(_materialNames[parseInt(mstr)]);
                }
                
                if (uv.length != 0) {
                    uvD = new NumberUV(parseFloat(uv[0]), 1 - parseFloat(uv[1]));
                    uvC = new NumberUV(parseFloat(uv[2]), 1 - parseFloat(uv[3]));
                    uvB = new NumberUV(parseFloat(uv[4]), 1 - parseFloat(uv[5]));
                    uvA = new NumberUV(parseFloat(uv[6]), 1 - parseFloat(uv[7]));
                } else {
                    uvD = new NumberUV(0, 0);
                    uvC = new NumberUV(1, 0);
                    uvB = new NumberUV(0, 1);
                    uvA = new NumberUV(1, 1);
                }
                face = new Triangle3D(this, [a, b, c], material, [uvA, uvB, uvC]);
                faces.push(face);
                face = new Triangle3D(this, [c, d, a], material, [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 Triangle3D(this, [c, b, a], material, [uvC, uvB, uvA]);
                    faces.push(face);
                    face = new Triangle3D(this, [a, d, c], material, [uvA, uvD, uvC]);
                    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);
        }
    }
//}