X_Loader β版

DirectX で使われている XFILE Loader のβ版です。

前田稔の超初心者のプログラム入門

プログラムの作成

  1. XFILE Loader α版では Frame 構造とアニメーションの設定は無視していました。
    また XFILE Frame Animation では DirectX の Viewer に対応しているとは言え、専用のファイルを作成しました。
    今回は、よりもっと一般的な XFILE Loader を作成します。
    XFILE Loader のβ版 x_loader.java です。
    メインプログラムと同じフォルダーに置いてコンパイルして下さい。
    //★ XFILE Loader β版    前田 稔 ★
    import java.awt.*;
    import javax.swing.*;
    import javax.media.j3d.*;
    import javax.vecmath.*;
    import com.sun.j3d.utils.universe.*;
    import com.sun.j3d.utils.geometry.*;
    import java.io.*;
    import java.net.URL;
    import com.sun.j3d.loaders.*;
    import com.sun.j3d.utils.behaviors.mouse.*;
    import java.util.*;
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import com.sun.j3d.utils.image.TextureLoader;
    import com.sun.j3d.utils.behaviors.interpolators.*;
    
    // X-File Loader Object Class
    public class x_loader extends LoaderBase
    {
        public static final int AUTO = 1;
        SceneBase       Base;           // SceneBase
        int             FLAG;           // Loader Flag
        float           SIZE;           // Loader Size
    
        // 入力関係
        URL             PATH= null;     // ファイルの PATH
        BufferedReader  BR;             // 入力 Reader
        String          BUF;            // 1行分の入力バッファ
        StringTokenizer token;          // トークン Object
        String          STR;            // トークンの領域
    
        // Frame 構造体
        class  frame
        {   String          Name;       // Frame Name
            Matrix4f        Mat4;       // 変換マトリックス
            TransformGroup  FTran;      // Frame の Transform
            TransformGroup  BTran;      // Bone の Transform
            TransformGroup  MTran;      // Bone Mesh の Transform
            int             Mesh;       // Frame 内の Mesh 番号
            ArrayList<frame>Link;       // Frame のリンク
    
            // Constructor
            public frame(String name)
            {   Name= name;
                Mat4= new Matrix4f();
                FTran= new TransformGroup();
                FTran.setTransform(new Transform3D());
                BTran= null;
                MTran= null;
                Mesh= -1;
                Link= new ArrayList<frame>();
            }
    
            // 現在の Frame の Link に frm を追加する(frm を return)
            public frame SetLink(frame frm)
            {   Link.add(frm);
                return frm;
            }
        }
        frame       TOP;        // Frame 構造体の先頭("Size")
        frame       FRM;        // Frame 構造体の末尾
        frame       FWK;        // 作業用 Frame 構造体
        frame       TRN;        // "Trans" Frame
        frame       ROT;        // "Rot" Frame
    
        // MeshList 構造体
        class  meshlist
        {   String                  Name;   // Mesh Name
            ArrayList<Point3f>      vp;     // 頂点座標
            ArrayList<Vector3f>     vn;     // 法線ベクトル
            ArrayList<TexCoord2f>   vt;     // テクスチャ座標
            ArrayList<Integer>      idx;    // Index(ポリゴン)の区切り
            ArrayList<Integer>      xvp;    // 頂点 Index の並び
            ArrayList<Integer>      xvn;    // 法線 Index の並び
            ArrayList<Integer>      fidx;   // face Index
            ArrayList<matlist>      fmat;   // face Material
    
            // Constructor
            public meshlist(String name)
            {   Name = name;
                vp = new ArrayList<Point3f>();
                vn = new ArrayList<Vector3f>();
                vt = new ArrayList<TexCoord2f>();
                idx = new ArrayList<Integer>();
                xvp = new ArrayList<Integer>();
                xvn = new ArrayList<Integer>();
                fidx = new ArrayList<Integer>();
                fmat = new ArrayList<matlist>();
            }
        }
        ArrayList<meshlist>     MESH = new ArrayList<meshlist>();
    
        // Bone(SkinWeights)構造体
        class  bonelist
        {   String              Name;       // Bone Name
            int                 Mesh;       // Mesh の番号
            int                 NumIdx;     // idx の個数
            ArrayList<Integer>  Idx;        // 頂点 Index
            Matrix4f            Mat4;       // 変換マトリックス
    
            // Constructor
            public bonelist(int mesh)
            {   Mesh = mesh;
                NumIdx= 0;
                Mat4 = new Matrix4f(1.0f,0.0f,0.0f,0.0f, 0.0f,1.0f,0.0f,0.0f,
                                    0.0f,0.0f,1.0f,0.0f, 0.0f,0.0f,0.0f,1.0f);
            }
        }
        boolean         Skin = false;   // (SkinWeights を検出
        ArrayList<bonelist>     BONE = new ArrayList<bonelist>();
    
        // Material 構造体
        class  matlist
        {   String          Name;
            Color4f         faceColor;
            float           power;
            Color3f         specularColor;
            Color3f         emissiveColor;
            BufferedImage   image;
    
            // Constructor
            public matlist(String name)
            {   Name= name;
            }
        }
        ArrayList<matlist>      MAT = new ArrayList<matlist>();
    
        // Anime(Interpolator) 構造体
        class  animelist
        {   String              Name;   // Anime Name
            int                 PN0;    // 回転パスの数
            int                 PN1;    // スケールパスの数
            int                 PN2;    // 座標パスの数
            int                 PN3;    // 変換行列パスの数
            ArrayList<Float>    KW0;    // 回転の変化率
            ArrayList<Float>    KW1;    // スケールの変化率
            ArrayList<Float>    KW2;    // 座標の変化率
            ArrayList<Float>    KW3;    // 変換行列の変化率
            ArrayList<Quat4f>   DT0;    // 回転のパス
            ArrayList<Point3f>  DT1;    // スケールのパス
            ArrayList<Point3f>  DT2;    // 座標のパス
            ArrayList<Matrix4f> DT3;    // 変換行列のパス
    
            // Constructor
            public animelist()
            {   Name= "NoName";
                PN0=PN1=PN2=PN3= 0;
                KW0= new ArrayList<Float>();
                KW1= new ArrayList<Float>();
                KW2= new ArrayList<Float>();
                KW3= new ArrayList<Float>();
                DT0= new ArrayList<Quat4f>();
                DT1= new ArrayList<Point3f>();
                DT2= new ArrayList<Point3f>();
                DT3= new ArrayList<Matrix4f>();
            }
        }
        boolean         Anime = false;  // AnimationSet を検出
        ArrayList<animelist>    ANIME = new ArrayList<animelist>();
    
        // Constructor
        public x_loader()
        {   FLAG= 0;
        }
        public x_loader(int flag)
        {   FLAG= flag;
        }
        public x_loader(float size)
        {   FLAG= 2;
            SIZE= size;
        }
    
        @Override
        public Scene load(String fname)
        {   Base = new SceneBase();
            try
            {   File    file = new File(fname);
                java.net.URI  uri = file.toURI();
                PATH = uri.toURL();
                BR = new BufferedReader(new FileReader(file));
            }
            catch(IOException e)
            {   System.out.println("File Open Error=" + fname);
                return Base;
            }
            Create();
            return Base;
        }
    
        @Override
        public Scene load(URL aURL)
        {   Base = new SceneBase();
            if (PATH == null)   PATH= aURL;
            try
            {   InputStream istream = aURL.openStream();
                InputStreamReader ireader = new InputStreamReader(istream);
                BR = new BufferedReader(ireader);
            }
            catch(IOException e)
            {   System.out.println("URL Open Error" + aURL);
                return Base;
            }
            Create();
            return Base;
        }
    
        @Override
        public Scene load(Reader reader)
        {   Base = new SceneBase();
            BR = new BufferedReader(reader);
            Create();
            return Base;
        }
    
        // Scene を生成
        public void Create()
        {   Base.setSceneGroup(new BranchGroup());
    
            // サイズ Frame
            TOP=FRM= new frame("Size");
            // 移動 Frame
            FWK=TRN= new frame("Trans");
            FRM= FRM.SetLink(FWK);
            // 回転 Frame
            FWK=ROT= new frame("Rot");
            FRM= FRM.SetLink(FWK);
            Base.getSceneGroup().addChild(TOP.FTran);
    
            Read_Xfile();
            if (FLAG!=0)            // フラグの設定
            {   Resize();  }
        }
    
        // X-File を入力してモデルを解析
        public void Read_Xfile()
        {
            // Header Check
            NextRead();
            STR = token.nextToken();
            if ("xof".equals(STR)==false)
            {   System.out.println("X-File error?" + STR);
                return;
            }
            STR = token.nextToken();
            if ("txt".equals(STR.substring(4,7))==false)
            {   System.out.println("X-File error?" + STR);
                return;
            }
    
            MeshPath(FRM);                  // Frame, Mesh, Bone の設定
            if (Skin)   Create_Skin();      // "SkinWeights" が使われたとき
            else    Create_Model(TOP);      // Frame 内に設定されたモデルを生成
            if (Anime)                      // "AnimationSet" を検出
            {   AnimePath();                // AnimationSet を解析して Anime List に格納する
                SetInterpolator();          // Interpolator を設定
            }
            Tran_Link(TOP);                 // Frame の先頭から TransformGroup を Link する
            Close();
        }
    
        // Frame(及び含まれる Mesh)を解析する
        public void MeshPath(frame frm)
        {   int     i,j,num,pn;
    
            if (Anime)  return;                 // AnimationSet を検出
            while(NextRead())
            {
                STR = token.nextToken();
                if ("Frame".equals(STR))        // Frame を設定
                {   Set_Frame(frm); }
    
                if ("Material".equals(STR))     // マテリアルの登録
                {   STR = token.nextToken();    // STR= ID
                    if ("{".equals(STR))        // ID が無い(普通は起こらない)
                    {   MAT.add(matset("NoName"));  }
                    else
                    {   Skip("{");
                        MAT.add(matset(STR));   // mtbl に登録
                    }
                }
    
                if ("Mesh".equals(STR))     // 頂点座標
                {   Set_Mesh(frm);          // Frame に登録
                }
    
                if ("AnimationSet".equals(STR)) // AnimationSet
                {   Anime = true;
                    return;
                }
            }
        }
    
        // Frame を設定
        public void Set_Frame(frame frm)
        {   frame   fw= frm;
            if (Anime)  return;     // AnimationSet を検出
            STR = token.nextToken();
            if ("{".equals(STR))    FWK= new frame("NoName");
            else    FWK= new frame(STR);
            fw= frm.SetLink(FWK);
    
            STR = Word();
            if ("FrameTransformMatrix".equals(STR))
            {   Skip("{");
                FWK.Mat4= SetMat4();
                Transform3D trans4 = new Transform3D(FWK.Mat4);
                FWK.FTran.setTransform(trans4);
                Skip("}");
                NextRead();
                STR = Word();
            }
    
            // STR == 次のキーワード
            while(true)
            {
                if ("}".equals(STR))
                {   return; }
                if ("Frame".equals(STR))
                {   Set_Frame(fw);              // 再帰的に呼び出す
                }
                if ("Mesh".equals(STR))         // 頂点座標
                {   Set_Mesh(fw);               // Frame に登録
                }
                if ("AnimationSet".equals(STR)) // AnimationSet
                {   Anime = true;
                    return;
                }
                NextRead();
                STR = token.nextToken();
            }
        }
    
        // Mesh 情報を収集して MESH List に登録
        public void Set_Mesh(frame frm)
        {   int         i,j,num,pn;
            meshlist    mesh;
    
            STR = token.nextToken();
            if ("{".equals(STR))    mesh= new meshlist("NoName");
            else
            {   mesh= new meshlist(STR);
                Skip("{");
            }
    
            // Mesh の情報
            num= Val();             // 頂点の数
            for(i=0; i<num; i++)    mesh.vp.add(new Point3f(FVal(), FVal(), FVal()));
            num= Val();             // ポリゴンの数
            for(i=0; i<num; i++)
            {   pn= Val();          // ポリゴンの頂点数
                mesh.idx.add(pn);
                for(j=0; j<pn; j++) mesh.xvp.add(Val());
            }
    
            // 法線/テクスチャ/マテリアル
            while(NextRead())
            {
                STR = token.nextToken();
                if ("MeshNormals".equals(STR))  // 法線ベクトル
                {   num= Val();             // 法線の数
                    for(i=0; i<num; i++)    mesh.vn.add(new Vector3f(FVal(), FVal(), FVal()));
                    num= Val();             // 面の数
                    for(i=0; i<num; i++)
                    {   pn= Val();          // ポリゴンの頂点数
                        for(j=0; j<pn; j++) mesh.xvn.add(Val());
                    }
                    Skip("}");
                }
                if ("MeshTextureCoords".equals(STR))  // テクスチャ座標
                {   num= Val();             // 座標の数
                    for(i=0; i<num; i++)    mesh.vt.add(new TexCoord2f(FVal(), FVal()));
                    Skip("}");
                }
                if ("MeshMaterialList".equals(STR)) // マテリアルリスト
                {   num= Val();                 // マテリアルの数
                    pn= Val();                  // フェース(ポリゴン)の数
                    for(i=0; i<pn; i++)     mesh.fidx.add(Val());
                    for(i=0; i<num; i++)
                    {   NextRead();
                        STR = token.nextToken();
                        if ("Material".equals(STR))
                        {   Skip("{");          // MeshMaterialList で直接定義
                            mesh.fmat.add(matset(""));
                        }
                        if (STR.charAt(0)=='{') // {Matgr} { Matgr }
                        {   STR= EDword();
                            for(j=0; j<MAT.size() && STR.equals(MAT.get(j).Name)==false; j++);
                            if (j<MAT.size())   mesh.fmat.add(MAT.get(j));
                            else
                            {   System.out.println("not found MaterialID=" + STR);
                                mesh.fmat.add(MAT.get(0));  // 仮に設定
                            }
                        }
                    }
                    Skip("}");
                }
                if ("SkinWeights".equals(STR))      // SkinWeights
                {   Set_Skin(MESH.size());          // 処理中の Mesh 番号を渡す
                    Skip("}");
                }
                if ("XSkinMeshHeader".equals(STR))  // SkinHeader
                {   Skip("}");
                }
                if ("VertexDuplicationIndices".equals(STR)) // VertexDuplicationIndices
                {   Skip("}");
                }
                if ("MeshVertexColors".equals(STR)) // MeshVertexColors
                {   Skip("}");
                }
                if ("}".equals(STR))            // Mesh の終り
                {   break;  }
                if ("AnimationSet".equals(STR)) // AnimationSet
                {   Anime = true;
                    break;
                }
                if ("Frame".equals(STR))        // Frame
                {   break;  }
            }
            MESH.add(mesh);                     // Mesh List に登録
            frm.Mesh= MESH.size()-1;            // Frame の Mesh 番号
        }
    
        // Bone(SkinWeights)の設定(BUF="SkinWeights")
        public void Set_Skin(int msh)
        {   int         i;
            bonelist    bn;
    
            Skin= true;             // Skin Flag ON
            bn= new bonelist(msh);
            Skip("{");
            NextRead();
            STR= token.nextToken();
            bn.Name= EDword();
            NextRead();
            bn.NumIdx= Val();
            NextRead();
            bn.Idx= new ArrayList<Integer>();
            for(i=0; i<bn.NumIdx; i++)  bn.Idx.add(Val());
            NextRead();
            for(i=0; i<bn.NumIdx; i++)  FVal();
            NextRead();
            bn.Mat4= SetMat4();
            BONE.add(bn);
        }
    
        // AnimationSet を解析して Anime List に格納する
        public void AnimePath()
        {
            while(NextRead())
            {   STR= token.nextToken();
                if ("Animation".equals(STR))
                {   if (SetAnime()==false) return;  }
            }
        }
    
        // "Animation" を検出(Anime List に情報を格納する)
        public boolean SetAnime()
        {   animelist   anime= new animelist();
            int     type,siz;
            int     i,j,k;
            float[] q4= new float[16];
    
            Skip("{");
            while(NextRead())
            {   STR= token.nextToken();
                if (STR.charAt(0)=='{')         // Bone Name
                {   STR= EDword();
                    anime.Name= STR;
                }
                if ("AnimationKey".equals(STR)) // Animation  Key の設定
                {
                    type= Val();    // TYPE(0,1,2)
                    NextRead();
                    siz= Val();     // キーの数
    
                    if (type==0)    // 回転 Animation
                    {   anime.PN0= siz;
                        for(i=0; i<siz; i++)
                        {   anime.KW0.add(FVal());
                            k= Val();               // k=Quat4f の4
                            for(j=0; j<4; j++)
                            {   q4[j]= FVal();  }
                            anime.DT0.add(new Quat4f(q4[0],q4[1],q4[2],q4[3]));
                        }
                    }
                    if (type==1)    // スケール Animation
                    {   anime.PN1= siz;
                        for(i=0; i<siz; i++)
                        {   anime.KW1.add(FVal());  // Anime Time
                            k= Val();               // k=Scale3f の3
                            for(j=0; j<3; j++)
                            {   q4[j]= FVal();  }
                            anime.DT1.add(new Point3f(q4[0],q4[1],q4[2]));
                            //NextRead();
                        }
                    }
                    if (type==2)    // 移動 Animation
                    {   anime.PN2= siz;
                        for(i=0; i<siz; i++)
                        {   anime.KW2.add(FVal());  // Anime Time
                            k= Val();               // k=Point3f の3
                            for(j=0; j<3; j++)
                            {   q4[j]= FVal();  }
                            anime.DT2.add(new Point3f(q4[0],q4[1],q4[2]));
                        }
                    }
                    if (type==3)    // 変換行列 Animation
                    {   anime.PN3= siz;
                        for(i=0; i<siz; i++)
                        {   anime.KW3.add(FVal());  // Anime Time
                            k= Val();               // k=変換行列の16
                            for(j=0; j<16; j++)
                            {   q4[j]= FVal();  }
                            anime.DT3.add(new Matrix4f(q4));
                        }
                    }
                    Skip("}");
                }
                if ("}".equals(STR))    break;
            }
            ANIME.add(anime);
            return true;
        }
    
        // Frame に Interpolator を設定する
        public void SetInterpolator()
        {   int         i,j,n;
            animelist   anime;
            ArrayList<Float>    WK= new ArrayList<Float>();
    
            for(n=0; n<ANIME.size(); n++)
            {   anime= ANIME.get(n);
                FWK= SrhName(TOP,anime.Name);   // Frame を検索
                if (FWK==null)
                {   System.out.println("Frame が見つかりません= [" + anime.Name + "]");
                    return;
                }
                FWK.BTran= new TransformGroup();    //☆ ボーン用の TransformGroup を生成
                FWK.BTran.setTransform(new Transform3D());
                // KW0,KW1,KW2,KW3 をソート&サマリー
                if (anime.PN0>0)    WK.addAll(anime.KW0);
                if (anime.PN1>0)    WK.addAll(anime.KW1);
                if (anime.PN2>0)    WK.addAll(anime.KW2);
                if (anime.PN3>0)    WK.addAll(anime.KW3);
                Float[] obj= (Float[])WK.toArray(new Float[0]);
                java.util.Arrays.sort(obj);
                j= 0;
                for(i=0; i<obj.length; i++)
                {   if (obj[j]<obj[i])
                    {   j++;
                        obj[j]= obj[i];
                    }
                }
    
                // ウエイト(0.0f 〜 1.0f)の計算
                int             PNUM= j;                // パスの数
                float[]         KNOTS= new float[PNUM]; // パスのウエイト
                float wf= obj[PNUM-1];
                for(i=0; i<PNUM; i++)   KNOTS[i]= obj[i]/wf;
    
                // Interpolator の領域を確保
                KBKeyFrame[]    keyframes = new KBKeyFrame[PNUM];
                int             ix0,ix1,ix2,ix3;
                Point3f         scale = new Point3f(1.0f, 1.0f, 1.0f);  // 大きさ
                Point3f         pos = new Point3f(0.0f, 0.0f, 0.0f);    // 座標
                float[]         euler = { 0.0f, 0.0f, 0.0f };           // オイラー角
    
                ix0=ix1=ix2=ix3= 0;
                for(i=0; i<PNUM; i++)
                {   if (anime.PN3>0 && ix3<anime.PN3)   // 変換行列を設定
                    {   if (i==0 || anime.KW3.get(ix3)<obj[i])
                        {   if (anime.KW3.get(ix3)<obj[i])  ix3++;
                            if (ix3<anime.PN3)
                            {   MatrixToEuler(anime.DT3.get(ix3),euler);
                                pos.x= anime.DT3.get(ix3).m03;
                                pos.y= anime.DT3.get(ix3).m13;
                                pos.z= anime.DT3.get(ix3).m23;
                            }
                        }
                    }
                    if (anime.PN0>0 && ix0<anime.PN0)   // クオータニオンを設定
                    {   if (i==0 || anime.KW0.get(ix0)<obj[i])
                        {   if (anime.KW0.get(ix0)<obj[i])  ix0++;
                            if (ix0<anime.PN0)  QuaternionToEuler(anime.DT0.get(ix0),euler);
                        }
                    }
                    if (anime.PN1>0 && ix1<anime.PN1)   // スケールを設定
                    {   if (i==0 || anime.KW1.get(ix1)<obj[i])
                        {   if (anime.KW1.get(ix1)<obj[i])  ix1++;
                            if (ix1<anime.PN1)  scale= anime.DT1.get(ix1);
                        }
                    }
                    if (anime.PN2>0 && ix2<anime.PN2)   // 座標を設定
                    {   if (i==0 || anime.KW2.get(ix2)<obj[i])
                        {   if (anime.KW2.get(ix2)<obj[i])  ix2++;
                            if (ix2<anime.PN2)  pos= anime.DT2.get(ix2);
                        }
                    }
                    keyframes[i]= new KBKeyFrame(KNOTS[i],0,pos,
                        euler[0],euler[1],euler[2],scale,0.0f,0.0f,0.0f);   //head,pitch,bank
                }
    
                FWK.BTran.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
                Alpha alpha = new Alpha(-1,(int)(obj[PNUM-1]/3));
                Transform3D axis = new Transform3D();
                BoundingSphere bounds = new BoundingSphere(new Point3d(),100.0);
                KBRotPosScaleSplinePathInterpolator interpolator =
                  new KBRotPosScaleSplinePathInterpolator(alpha,FWK.BTran,axis,keyframes);
                interpolator.setSchedulingBounds(bounds);
                FWK.BTran.addChild(interpolator);
            }
        }
    
        // クオータニオン⇒オイラー角(head,pitch,bank)
        public static void QuaternionToEuler(Quat4f quat, float[] euler)
        {   Matrix4f    Mat4= new Matrix4f();
    
            Mat4.set(quat);
            MatrixToEuler(Mat4,euler);
            if (euler[0]>3.14f)     euler[0]= 3.14f;
            if (euler[0]<-3.14f)    euler[0]= -3.14f;
            if (euler[2]>3.14f)     euler[2]= 3.14f;
            if (euler[2]<-3.14f)    euler[2]= -3.14f;
            if (euler[1]>1.57f)     euler[0]= 1.57f;
            if (euler[1]<-1.57f)    euler[0]= -1.57f;
        }
    
        // 変換行列⇒オイラー角
        public static void MatrixToEuler(Matrix4f mat4, float[] euler)
        {   double  yaw,pitch,roll;
    
            roll= Math.atan2((double)mat4.m10, (double)mat4.m11);
            pitch= Math.asin((double)-mat4.m12);
            yaw= Math.atan2((double)mat4.m02, (double)mat4.m22);
            if (Math.abs(Math.cos(pitch)) < 1.0e-6f)
            {   if(mat4.m10>0.0f)   roll= Math.PI;
                else                roll= -Math.PI;
                if(mat4.m02>0.0f)   yaw= Math.PI;
                else                yaw= -Math.PI;
            }
            euler[0]= (float)yaw;
            euler[1]= (float)pitch;
            euler[2]= (float)roll;
        }
    
        // frame Name で検索
        public frame SrhName(frame frm, String nam)
        {   int     i;
            if (frm==null)  return null;
            if (nam.compareToIgnoreCase(frm.Name)==0)   return frm;
            for(i=0; i<frm.Link.size(); i++)
            {   FWK= SrhName(frm.Link.get(i),nam);
                if (FWK!=null)  return FWK;
            }
            return null;
        }
    
        // Frame を検索して、Frame 内で定義されたモデルを生成
        public void Create_Model(frame frm)
        {   int     i;
            if (frm==null)  return;
            if (frm.Mesh>=0)    Create_Mesh(frm,null);
            for(i=0; i<frm.Link.size(); i++)
            {   Create_Model(frm.Link.get(i));  }
        }
    
        // Frame の先頭から TransformGroup を Link する
        // Bone が設定されているときは FTran⇒BTran⇒次のFTran
        // Mesh が設定されているときは FTran⇒MTran or BTran⇒MTran
        public void Tran_Link(frame frm)
        {   int     i;
    
            if (frm==null)  return;
            if (frm.BTran!=null)    frm.FTran.addChild(frm.BTran);
            if (frm.MTran!=null)
            {   if (frm.BTran==null)    frm.FTran.addChild(frm.MTran);
                else    frm.BTran.addChild(frm.MTran);
            }
            for(i=0; i<frm.Link.size(); i++)
            {   if (frm.BTran==null)
                {   frm.FTran.addChild(frm.Link.get(i).FTran);
                }
                else
                {   frm.BTran.addChild(frm.Link.get(i).FTran);
                }
                Tran_Link(frm.Link.get(i));
            }
        }
    
        // Bone(SkinWeights) の Index で指定されたポリゴンを生成
        public void Create_Skin()
        {   bonelist    bone;
            int         i;
            for(i=0; i<BONE.size(); i++)
            {   bone= BONE.get(i);
                FWK= SrhName(TOP,bone.Name);    // ボーンを検索
                if (FWK==null)
                {   System.out.println("Frame が見つかりません= [" + bone.Name + "]");
                    return;
                }
    
                FWK.MTran= new TransformGroup();    //☆ アニメ用の TransformGroup を生成
                Transform3D trans4 = new Transform3D(bone.Mat4);
                FWK.MTran.setTransform(trans4);
                Create_Mesh(FWK, bone);         // bone を渡す
            }
        }
    
        // Material のセット
        public matlist matset(String str)
        {   String  wstr;
            matlist mat = new matlist(str);
    
            mat.faceColor = new Color4f(FVal(), FVal(), FVal(), FVal());
            mat.power = FVal();
            mat.specularColor = new Color3f(FVal(), FVal(), FVal());
            mat.emissiveColor = new Color3f(FVal(), FVal(), FVal());
            NextRead();
            wstr = token.nextToken();
            if ("TextureFilename".equals(wstr))
            {   // Texture の設定
                NextRead();
                wstr = token.nextToken();
                mat.image = loadImage(wstr);
                Skip("}");      // TextureFilename の終わり
                Skip("}");      // Material の終わり
            }
            return mat;
        }
    
        // Mesh を生成して Frame に登録
        // MTran!=null の時は MTran に、null の時は FTran に登録
        public void Create_Mesh(frame frm, bonelist bone)
        {   int     i,j,k,cnt,pt,n;
            boolean     skipf;
            meshlist    mesh;               // Mesh List 構造体
            TransformGroup  TGW;            // Transform
            Point3f[]   vw;                 // 頂点座標
            Vector3f[]  nw= null;           // 法線ベクトル
            matlist     matw= null;         // マテリアル
            TexCoord2f[]    tex= null;      // テクスチャ
    
            if (bone!=null)     mesh= MESH.get(bone.Mesh);
            else                mesh= MESH.get(frm.Mesh);
            if (frm.MTran!=null)    TGW = frm.MTran;
            else    TGW = frm.FTran;
            pt= 0;                          // 多角形の先頭Index
            for(i=0; i<mesh.idx.size(); i++)    // i=Frame(ポリゴン)の番号
            {
                cnt= mesh.idx.get(i);         // 多角形の角数
                vw= new Point3f[cnt];       // Frame の頂点座標
                tex = new TexCoord2f[cnt];  // Frame のテクスチャ座標
                skipf= false;
                for(j=0; j<cnt; j++)
                {   n= mesh.xvp.get(pt+j);       // 頂点Index
                    if (bone!=null)
                    {   // idxlst に登録されていないポリゴンは登録しない
                        for(k=0; k<bone.Idx.size() && bone.Idx.get(k)!=n; k++);
                        if (k>=bone.Idx.size())
                        {   skipf= true;
                            break;
                        }
                    }
                    if (skipf)  break;
                    if (n<mesh.vp.size())
                    {   vw[j]= mesh.vp.get(n);
                    }
                    else    System.out.println("頂点Index Error=" + n);
                    if (n<mesh.vt.size())
                    {   tex[j]= new TexCoord2f();
                        tex[j].x= mesh.vt.get(n).x;
                        tex[j].y= 1.0f-mesh.vt.get(n).y;
                    }
                }
                if (skipf)
                {   pt+= cnt;
                    continue;
                }
                if (0<mesh.xvn.size())
                {   nw = new Vector3f[cnt];
                    for(j=0; j<cnt; j++)
                    {   n= mesh.xvn.get(pt+j);  // 法線Index
                        if (n<mesh.vn.size())   nw[j]= mesh.vn.get(n);
                    }
                }
    
                if (mesh.vn.size()<1)   nw= null;
                if (mesh.vt.size()<1)   tex= null;
    
                if (mesh.fidx.size()>0)         // face Index が定義されているとき
                {   if (mesh.fidx.size()>i) n= mesh.fidx.get(i);
                    else n= mesh.fidx.get(0);   // 設定を超えるとゼロ番目
                    matw= mesh.fmat.get(n);
                }
                else    matw= null;
    
                face(TGW,vw,nw,matw,tex);
                pt+= cnt;
            }
        }
    
        // Face(Polygon) を生成
        public void face(TransformGroup trans, Point3f[] vertices, Vector3f[] normal, matlist mat, TexCoord2f[] texture)
        {
            int[] stripCount = new int[1];
            Color4f[]   colors = new Color4f[vertices.length];
            Color4f     col4f;
    
            stripCount[0] = vertices.length;
            if (mat!=null)  col4f= mat.faceColor;
            else    col4f= new Color4f(1.0f,1.0f,1.0f,1.0f);
            for(int i=0; i<vertices.length; i++)    colors[i]= col4f;
            // Face を作成
            GeometryInfo ginfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
            ginfo.setCoordinates(vertices);
            ginfo.setStripCounts(stripCount);
            ginfo.setColors(colors);
    
            // Texture の設定
            if (texture!=null && mat!=null && mat.image!=null)
            {   ginfo.setTextureCoordinateParams(1,2);  // 二次元のテクスチャ座標を一枚使う
                ginfo.setTextureCoordinates(0,texture); // 参照する次元(二次元のテクスチャ=0)
            }
    
            // 法線ベクトルの設定
            if (normal!=null)   ginfo.setNormals(normal);
            else
            {   NormalGenerator gen = new NormalGenerator();
                gen.generateNormals(ginfo);
            }
    
            // Material を設定して SceneBase(TransformGroup) に追加
            Shape3D shape = new Shape3D(ginfo.getGeometryArray());
            shape.setAppearance(createAppearance(mat));
            trans.addChild(shape);
        }
    
        // Material の設定
        private Appearance createAppearance(matlist para)
        {   Appearance ap = new Appearance();
            Material mat = new Material();
            Color3f cw= new Color3f(1.0f,1.0f,1.0f);
    
            if (para!=null)
            {   cw= new Color3f(para.faceColor.x,para.faceColor.y,para.faceColor.z);
                mat.setShininess(para.power);               // 輝度
                mat.setSpecularColor(para.specularColor);   // 鏡面反射
                mat.setEmissiveColor(para.emissiveColor);   // 発光
            }
            mat.setDiffuseColor(cw);                        // 拡散反射による色
            Color3f     acw = new Color3f(cw.x*0.2f,cw.y*0.2f,cw.z*0.2f);
            mat.setAmbientColor(acw);                       // 環境光
            if (para!=null && para.image!=null)             // Texture の設定
            {   TextureLoader texload = new TextureLoader(para.image);
                Texture2D texture2d = (Texture2D)texload.getTexture();
                ap.setTexture(texture2d);
    
                TextureAttributes txattr = new TextureAttributes();
                txattr.setTextureMode(TextureAttributes.MODULATE);
                txattr.setTextureBlendColor(para.faceColor);
                ap.setTextureAttributes(txattr);
            }
            ap.setMaterial(mat);
            return ap;
        }
    
        // Model の Resize
        private void Resize()
        {   meshlist    ml;
            Transform3D scl= new Transform3D();
            float   xmn,xmx,ymn,ymx,xrate,yrate;
    
            if (FLAG==2)        // 拡大・縮小
            {   scl.setScale(SIZE);
                scl.setScale(SIZE);
                TOP.FTran.setTransform(scl);
                return;
            }
            ml= MESH.get(0);
            if (ml.vp.size()==0)   return;
            xmn=xmx= ml.vp.get(0).x;
            ymn=ymx= ml.vp.get(0).y;
            for(int i=1; i<ml.vp.size(); i++)
            {   if (xmn>ml.vp.get(i).x) xmn= ml.vp.get(i).x;
                if (xmx<ml.vp.get(i).x) xmx= ml.vp.get(i).x;
                if (ymn>ml.vp.get(i).y) ymn= ml.vp.get(i).y;
                if (ymx<ml.vp.get(i).y) ymx= ml.vp.get(i).y;
            }
            // 拡大・縮小
            xrate= 1.0f/(xmx-xmn);
            yrate= 1.0f/(ymx-ymn);
            if (xrate<yrate)    scl.setScale(xrate);
            else                scl.setScale(yrate);
            TOP.FTran.setTransform(scl);
            // 移動
            xrate= 0.0f - (xmx+xmn)/2.0f;
            yrate= 0.0f - (ymx+ymn)/2.0f;
            Transform3D t3d = new Transform3D();
            t3d.setTranslation(new Vector3d(xrate, yrate, 0.0));
            TRN.FTran.setTransform(t3d);
            // 回転 Frame
            Transform3D rotate = new Transform3D();
            rotate.rotY(Math.PI);   // Y 軸について180度回転
            ROT.FTran.setTransform(rotate);
        }
    
        // 変換行列の設定
        private Matrix4f SetMat4()
        {   Matrix4f    mat4;
            float[]     val= new float[16];
    
            for(int i=0; i<16; i++) val[i]= FVal();
            mat4= new Matrix4f( val[0],val[4],val[8],val[12],
                                val[1],val[5],val[9],val[13],
                                val[2],val[6],val[10],val[14],
                                val[3],val[7],val[11],val[15] );
            return mat4;
        }
    
        // Line Read(BUF に次の行を入力)
        private boolean LineRead()
        {
            try
            {   BUF = BR.readLine();  }
            catch(IOException e)
            {   System.out.println(e);  }
            if (BUF==null)  return false;
            return true;
        }
    
        // Next Read Line(空行をスキップ, /行をスキップ, Token を設定)
        private boolean NextRead()
        {
            while(LineRead())
            {   if (BUF.length()>0 && BUF.charAt(0)!='/')
                {   token = new StringTokenizer(BUF, " ,;\t", false);
                    if (token.hasMoreTokens())  return true;
                }
            }
            return false;
        }
    
        // BUF にトークンが無い時は、次の行を入力('{'だけの行をスキップ)
        // トークンの先頭が '/' の時は、次の行を入力
        private String Word()
        {   String  wk;
            while(true)
            {   if (token.hasMoreTokens()==true)
                {   wk = token.nextToken();
                    if ("{".equals(wk)) continue;
                    if (wk.charAt(0)!='/')  return wk;
                }
                NextRead();
            }
        }
    
        // STR の記号({}")で囲まれた Word を取り出す
        // STR=="{" のとき、次の行から取り出す('}' までスキップ)
        private String EDword()
        {   boolean flg= false;
    
            if ("{".equals(STR))
            {   flg= true;
                STR= Word();
            }
            if (STR.charAt(0)=='{')
            {   flg= true;
                STR= STR.substring(1);
            }
            if (STR.charAt(0)=='"') STR= STR.substring(1);
            if (STR.charAt(STR.length()-1)=='}')
            {   flg= false;
                STR= STR.substring(0,STR.length()-1);
            }
            if (STR.charAt(STR.length()-1)=='"')    STR= STR.substring(0,STR.length()-1);
            if (flg)    Skip("}");
            return STR;
        }
    
        // Next Float(BUF から次の値を取得)
        private float FVal()
        {   String  wk;
            wk = Word();
            if ("}".equals(wk))
            {   System.out.println("★Float Data の処理中に } を検出");
                return 0.0f;
            }
            return Float.parseFloat(wk);
        }
    
        // Next int(BUF から次の値を取得)
        private int Val()
        {   String  wk;
            wk = Word();
            if ("}".equals(wk))
            {   System.out.println("★Int Data の処理中に } を検出");
                return 0;
            }
            return Integer.parseInt(wk);
        }
    
        // key まで読み飛ばす
        private void Skip(String key)
        {   String  str;
            while(true)
            {   if (token.hasMoreTokens()==false)
                    if (NextRead()==false)  return;
                str = token.nextToken();
                if (str.equals(key))    return;
            }
        }
    
        // BUFferedReader Close
        public void Close()
        {   try
            {   BR.close();  }
            catch(IOException e)    {  System.out.println("File Close Error");  }
        }
    
        // BufferedImage の入力
        public BufferedImage loadImage(String fileName)
        {   BufferedImage img= null;
            String  wk = fileName;
            if (wk.charAt(0)=='"')  wk= fileName.substring(1,fileName.length()-1);
            try
            {   URL u = new URL(PATH, wk);
                img = ImageIO.read(u);
            }
            catch (IOException e)
            {  System.out.println("Image Open Error=" + fileName);  }
            return img;
        }
    }
    
  2. XFILE を入力してモデルを描画する main() プログラム View_Auto.java です。
    x_loader.java と同じフォルダーに置いてコンパイルして下さい。
    テストで使用する XFILE は XFILE Animation Model に掲載しています。
    //★ Skin Mesh のアニメーション    前田 稔
    import java.awt.*;
    import javax.swing.*;
    import javax.media.j3d.*;
    import javax.vecmath.*;
    import com.sun.j3d.utils.universe.*;
    import com.sun.j3d.loaders.*;
    import com.sun.j3d.utils.behaviors.mouse.*;
    
    public class View_Auto extends JFrame
    {   //static String  Model_name= "C:\\DATA\\Model\\char.x";
        static String  Model_name= "C:\\DATA\\Model\\BoxAnime.x";
        //static String  Model_name= "C:\\DATA\\Model\\boxmen4.x";
        //static String  Model_name= "C:\\DATA\\Model\\Barrel.x";
        //static String  Model_name= "C:\\DATA\\Model\\tiny.x";  //メモリ不足
    
        // main Method
        public static void main(String[] args)
        {   java.awt.EventQueue.invokeLater(new Runnable()
            {   public void run()
                {   new View_Auto().setVisible(true);  }
            });
        }
    
        // Constructor
        public View_Auto()
        {   // JFrame の初期化
            super("OBJ Model");
            setSize(512,512);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            // Java3D 関係の設定
            GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
            Canvas3D canvas = new Canvas3D(config);
            add(canvas);
    
            // SimpleUniverseを生成
            SimpleUniverse universe = new SimpleUniverse(canvas);
            universe.getViewingPlatform().setNominalViewingTransform();
    
            // Scene を生成
            universe.addBranchGraph(CreateScene());
        }
    
        // Scene の生成
        public BranchGroup CreateScene()
        {   BranchGroup objRoot = new BranchGroup();
    
            // Light の設定
            BoundingSphere bounds = new BoundingSphere(new Point3d(),100.0);
            DirectionalLight dlight =
              new DirectionalLight(true, new Color3f(1.0f,1.0f,1.0f), new Vector3f(0.3f,-0.3f,-0.3f));
            dlight.setInfluencingBounds(bounds);
            objRoot.addChild(dlight);
            AmbientLight alight = new AmbientLight();
            alight.setInfluencingBounds(bounds);
            objRoot.addChild(alight);
    
            // Mouse 操作の設定
            TransformGroup trans = new TransformGroup();
            SetMouse(objRoot, trans);
            objRoot.addChild(trans);
    
            // モデルの入力
            //x_loader f = new x_loader();
            x_loader f = new x_loader(x_loader.AUTO);
            Scene s = null;
            s = f.load(Model_name);
            trans.addChild(s.getSceneGroup());
    
            // 背景色の設定
            //Color3f bgColor = new Color3f(0.05f, 0.2f, 0.05f);
            Color3f bgColor = new Color3f(0.25f, 0.3f, 0.25f);
            Background bgNode = new Background(bgColor);
            bgNode.setApplicationBounds(bounds);
            objRoot.addChild(bgNode);
            return objRoot;
        }
    
        // Mouse 操作の設定
        public void SetMouse(BranchGroup objRoot, TransformGroup trans)
        {
            // Model の修正を許可
            trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
            trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    
            // 回転を設定
            BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
            MouseRotate rotator = new MouseRotate(trans);
            rotator.setSchedulingBounds(bounds);
            objRoot.addChild(rotator);
    
            // 移動を設定
            MouseTranslate translator = new MouseTranslate(trans);
            translator.setSchedulingBounds(bounds);
            objRoot.addChild(translator);
    
            // ズームを設定
            MouseZoom zoomer = new MouseZoom(trans);
            zoomer.setSchedulingBounds(bounds);
            objRoot.addChild(zoomer);
       }
    }
    

プログラムの説明

  1. x_loader.java の基本的な説明は X_Loader α版 を参照して下さい。
    また XFILE アニメーションの基本的な説明は XFILE Frame Animation を参照して下さい。
    XFILE Frame Animation でも述べましたが、DirectX のモデルは左手座標系で、Java のモデルは右手座標系です。
    また DirectX では、一体のモデルの頂点座標を操作してアニメーションするのに対して、このプログラムはパーツ毎に インタープローラを設定してアニメーションしています。
    従って DirectX とはモデルの動きに多少の違いが生じることを御承知置き下さい。
    座標系の説明は Windows Guid を参照して下さい。
    当初 DirectX に添付されている tiny.x の描画を目標に始めたのですが、残念ながら描画するにはメモリが足りませんでした。
    java -Xrs -Xms5124m -Xmx512m でメモリを拡張できるようなのですが、それでも tiny.x は大きすぎるようです。 (^_^;)
    一般的に出回っている XFILE の形式は多種多様で、このプログラムでは描画出来ないものもあると思います。
    基本的に DirectX の Viewer に合わせていますが、機能を省略したり、独自の機能を追加したりしています。
    テスト用の XFILE は XFILE Animation Model から提供しています。
    ページ先頭の画像は、ネットからダウンロードした「樽が壊れるアニメーションモデル」です。
  2. コーディングの内容を説明するのは煩雑すぎるので、構造体を通じて処理の概要を明らかにします。
    構造体を理解するとプログラムの構成が見えて来ると思います。
    当初 Frame 構造体に全ての情報を集約する予定でしたが、tiny.x ではボーンの名前に「これから定義される Frame の名前」が使われていました。
    これでは Frame 構造体にボーンの情報を格納することが出来ません。
    そこで機能単位に構造体を分けて定義することにしました。
  3. プログラムをテストする適当な XFILE が見つかりません。
    やっとの思いで Barrel.x を見つけたのですが、素敵なモデルをお持ちの方はぜひ使わせて下さい。

【NOTE】

  1. Create() が Scene を生成するメインのメソッドです。
    モデルをサイズ調整して中央に配置し、正面から描画するために、サイズと移動と回転の Frame を生成します。
  2. Read_Xfile() メソッドが XFILE を入力してモデルを解析するメソッドです。
    ヘッダーをチェックして MeshPath(FRM) で Mesh を解析します。
    "SkinWeights" が使われたとき、Create_Skin() で Bone(SkinWeights) の Index で指定されたポリゴンを生成します。
    "SkinWeights" が使われていないとき、Create_Model(TOP) で Mesh が定義された Frame に Mesh を生成します。
    アニメーションが設定されているとき、AnimePath() で Animation を解析して SetInterpolator() で Interpolator を設定します。
  3. MeshPath(FRM) メソッドで Frame 階層と、それに含まれる Mesh を解析します。
    1. "Frame" が検出されると Set_Frame(frm) メソッドで Frame を解析します。
      frame 構造体は、親子孫の階層構造になっています。
    2. "Material" が検出されると matset(STR) で Material を解析します。
      解析した Material 構造体を MAT.add(matset(STR)) で Material List に登録します。
      Material は Frame や Mesh とは独立した形で定義されています。
    3. "Mesh" が検出されると Set_Mesh(frm) メソッドでメッシュの情報を解析して frm に生成します。
      Mesh は、例えば Frame_ModelDef の中で定義されます。
    4. "AnimationSet" が検出されると MeshPath は終了します。
      この後からアニメーションの定義が始まるのですが、ファイルの後部に追加されるような形になっています。
  4. Set_Frame(frm) メソッドで Frame と Frame に含まれる Mesh を解析します。
    1. Frame に変換行列が設定されていれば、構造体に格納します。
    2. "Frame" が検出されると、Set_Frame(frm) を再帰的に呼び出します。
    3. "Mesh" が検出されると、Set_Mesh(fw) で Mesh を解析します。
    4. "AnimationSet" が検出されるとメソッドは終了します。
  5. Set_Mesh(frm) メソッドで Mesh の情報を収集して MESH List に登録します。
    1. Mesh の頂点座標とポリゴン情報を List に格納します。
    2. "MeshNormals" が検出されると、法線情報を List に格納します。
    3. "MeshTextureCoords" が検出されると、テクスチャ情報を List に格納します。
    4. "MeshMaterialList" が検出されると、マテリアル情報を List に格納します。
    5. "SkinWeights" が検出されると、Set_Skin(MESH.size()) で Skin 情報を解析します。
    6. "XSkinMeshHeader" と "VertexDuplicationIndices" と "MeshVertexColors" はスキップします。
    7. "Frame" や "AnimationSet" が検出されるとメソッドは終了します。
  6. AnimePath() メソッドで AnimationSet の解析を行います。
    アニメーションの定義は、モデルの記述が終わった後に定義されています。
    "Animation" が検出されると SetAnime() で解析して Anime List に登録します。
  7. SetInterpolator() メソッドで Interpolator を設定します。
    Anime List に基づいて、Frame の TransformGroup に Interpolator を設定します。
    Interpolator を設定すると変換行列は効かないようです。
    新しい TransformGroup を生成して、SkinWeights の変換行列とポリゴンを格納します。
    このプログラムでは KBRotPosScaleSplinePathInterpolator を使ってアニメーションを行います。

超初心者のプログラム入門(Java2)