DirectX で使われている XFILE Loader のα版です。
DirectX と Java では座標系の違いにより左右が逆転します。

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

XFILE Loader

  1. XFILE Loader のα版 x_loader.java です。
    メインプログラムと同じフォルダーに置いてコンパイルして下さい。
    //★ x_loader  Alpha 版    前田 稔
    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;
    
    //★ X-File Loader Object Class
    public class x_loader extends LoaderBase
    {
        public static final int RESIZE = 1;
        SceneBase       Base;          // SceneBase
        int             FLAG;          // Loader Flag
        TransformGroup  Rot;           // 回転
        TransformGroup  Trans;         // 中央移動
        TransformGroup  Size;          // サイズ調整
    
        // 入力関係
        URL             PATH= null;    // ファイルの PATH
        BufferedReader  BR;            // 入力 Reader
        String          BUF;           // 1行分の入力バッファ
        StringTokenizer token;         // トークン Object
    
        // 頂点座標領域
        ArrayList<Point3f>    vp = new ArrayList<Point3f>();    // 頂点座標
        ArrayList<Vector3f>   vn = new ArrayList<Vector3f>();   // 法線ベクトル
        ArrayList<TexCoord2f> vt = new ArrayList<TexCoord2f>(); // テクスチャ座標
        ArrayList<Integer>    idx = new ArrayList<Integer>();   // Index(ポリゴン)の区切り
        ArrayList<Integer>    xvp = new ArrayList<Integer>();   // 頂点 Index の並び
        ArrayList<Integer>    xvn = new ArrayList<Integer>();   // 法線 Index の並び
    
        // Material 領域
        ArrayList<String>     mid = new ArrayList<String>();    // mtbl の ID
        ArrayList<mats>       mtbl = new ArrayList<mats>();     // Material Table
        // MaterialList 領域
        ArrayList<Integer>    fidx = new ArrayList<Integer>();  // face Index
        ArrayList<mats>       fmat = new ArrayList<mats>();     // face Material
    
        // Material 構造体
        class  mats
        {   Color4f         faceColor;
            float           power;
            Color3f         specularColor;
            Color3f         emissiveColor;
            BufferedImage   image;
        }
    
        // Constructor
        public x_loader()
        {   FLAG= 0;
        }
        public x_loader(int flag)
        {   FLAG= flag;
        }
    
        @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());
    
            // 回転の初期化
            Rot = new TransformGroup();
            Transform3D rotate = new Transform3D();
            rotate.rotY(Math.PI);       // Y 軸について180度回転
            Rot.setTransform(rotate);
    
            // 移動の初期化
            Trans = new TransformGroup();
            Transform3D t3d = new Transform3D();
            t3d.setTranslation(new Vector3d(0.0, 0.0, 0.0));
            Trans.setTransform(t3d);
    
            // サイズの初期化
            Size = new TransformGroup();
            Transform3D scl = new Transform3D();
            scl.setScale(1.0);
            Size.setTransform(scl);
    
            Trans.addChild(Rot);
            Size.addChild(Trans);
            Base.getSceneGroup().addChild(Size);
    
            set_data();
            creatscene();
            if ((FLAG&1) == 1)  // RESIZE フラグ ON
            {   Resize();  }
        }
    
        // X-File を入力して ArrayList に格納
        public void set_data()
        {   int     i,j,num,pn;
            String  str;
    
            // 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;
            }
    
            while(NextRead())
            {
                str = token.nextToken();
                if ("Mesh".equals(str))     // 頂点座標
                {   Skip("{");
                    num= Val();             // 頂点の数
                    for(i=0; i<num; i++)    vp.add(new Point3f(FVal(), FVal(), FVal()));
                    num= Val();             // ポリゴンの数
                    for(i=0; i<num; i++)
                    {   pn= Val();          // ポリゴンの頂点数
                        idx.add(pn);
                        for(j=0; j<pn; j++) xvp.add(Val());
                    }
                }
                if ("MeshNormals".equals(str))  // 法線ベクトル
                {   Skip("{");
                    num= Val();             // 法線の数
                    for(i=0; i<num; i++)    vn.add(new Vector3f(FVal(), FVal(), FVal()));
                    num= Val();             // 面の数
                    for(i=0; i<num; i++)
                    {   pn= Val();          // ポリゴンの頂点数
                        for(j=0; j<pn; j++) xvn.add(Val());
                    }
                }
                if ("MeshTextureCoords".equals(str))  // テクスチャ座標
                {   Skip("{");
                    num= Val();             // 座標の数
                    for(i=0; i<num; i++)    vt.add(new TexCoord2f(FVal(), FVal()));
                }
                if ("Material".equals(str)) // マテリアル
                {   str = token.nextToken();
                    if ("{".equals(str))    // ID が無い(普通は起こらない)
                    {   mtbl.add(matset());  }
                    else
                    {   mid.add(str);       // mtbl の ID
                        Skip("{");
                        mtbl.add(matset()); // mtbl に登録
                    }
                }
                if ("MeshMaterialList".equals(str)) // マテリアルリスト
                {   Skip("{");
                    num= Val();             // マテリアルの数
                    pn= Val();              // フェース(ポリゴン)の数
                    for(i=0; i<pn; i++)     fidx.add(Val());
                    for(i=0; i<num; i++)
                    {   NextRead();
                        str = token.nextToken();
                        if ("Material".equals(str))
                        {   Skip("{");          // MeshMaterialList で直接定義
                            fmat.add(matset());
                        }
                        if (str.charAt(0)=='{') // {Matgr} { Matgr }
                        {   String  wk;
                            if ("{".equals(str))    wk= token.nextToken();
                            else                    wk= str.substring(1,str.length()-1);
                            for(j=0; j<mid.size() && wk.equals(mid.get(j))==false; j++);
                            if (j<mid.size())   fmat.add(mtbl.get(j));
                            else
                            {   System.out.println("not found MaterialID=" + wk);
                                fmat.add(mtbl.get(0));  // 仮に設定
                            }
                            if ("{".equals(str))    Skip("}");
                        }
                    }
                }
            }
            Close();
        }
    
        // Material のセット
        public mats matset()
        {   String  wstr;
            mats mat = new mats();
    
            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;
        }
    
        // Scene を生成
        public void creatscene()
        {   int i,j,cnt,pt,n;
    
            Point3f[] vw;                   // 頂点座標
            TexCoord2f[] tex;               // テクスチャ
            Vector3f[] nw= new Vector3f[0]; // 法線ベクトル
            pt= 0;                          // 多角形の先頭Index
            for(i=0; i<idx.size(); i++)     // i=Frame(ポリゴン)の番号
            {
                cnt= idx.get(i);            // 多角形の角数
                vw= new Point3f[cnt];       // Frame の頂点座標
                tex = new TexCoord2f[cnt];  // Frame のテクスチャ座標
                for(j=0; j<cnt; j++)
                {   n= xvp.get(pt+j);       // 頂点Index
                    if (n<vp.size())    vw[j]= vp.get(n);
                    else    System.out.println("頂点Index Error=" + n);
                    if (n<vt.size())
                    {   tex[j]= new TexCoord2f();
                        tex[j].x= vt.get(n).x;
                        tex[j].y= 1.0f-vt.get(n).y;
                    }
                }
                if (0<xvn.size())
                {   nw = new Vector3f[cnt];
                    for(j=0; j<cnt; j++)
                    {   n= xvn.get(pt+j);   // 法線Index
                        if (n<vn.size())    nw[j]= vn.get(n);
                    }
                }
                if (vn.size()<1)      nw= null;
                if (vt.size()<1)      tex= null;
                n= fidx.get(i);             // マテリアルの Index
                if (n>=fmat.size())     n= 0;
                face(Rot,vw,nw,fmat.get(n),tex);
                pt+= cnt;
            }
        }
    
        // Face(Polygon) を生成
        public void face(TransformGroup trans, Point3f[] vertices, Vector3f[] normal, mats mat, TexCoord2f[] texture)
        {
            int[] stripCount = new int[1];
            Color4f[]   colors = new Color4f[vertices.length];
    
            stripCount[0] = vertices.length;
            for(int i=0; i<vertices.length; i++)    colors[i]= mat.faceColor;
            // Face を作成
            GeometryInfo ginfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
            ginfo.setCoordinates(vertices);
            ginfo.setStripCounts(stripCount);
            ginfo.setColors(colors);
    
            // Texture の設定
            if (mat.image!=null && texture!=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(mats para)
        {   Appearance ap = new Appearance();
            Material mat = new Material();
            Color3f     cw= new Color3f(para.faceColor.x,para.faceColor.y,para.faceColor.z);
            mat.setDiffuseColor(cw);                    // 拡散反射による色
            Color3f     acw = new Color3f(cw.x*0.2f,cw.y*0.2f,cw.z*0.2f);
            mat.setAmbientColor(acw);                   // 環境光
            mat.setShininess(para.power);               // 輝度
            mat.setSpecularColor(para.specularColor);   // 鏡面反射
            mat.setEmissiveColor(para.emissiveColor);   // 発光
            if (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()
        {   float   xmn,xmx,ymn,ymx,xrate,yrate;
            if (vp.size()==0)   return;
            xmn=xmx= vp.get(0).x;
            ymn=ymx= vp.get(0).y;
            for(int i=1; i<vp.size(); i++)
            {   if (xmn>vp.get(i).x)    xmn= vp.get(i).x;
                if (xmx<vp.get(i).x)    xmx= vp.get(i).x;
                if (ymn>vp.get(i).y)    ymn= vp.get(i).y;
                if (ymx<vp.get(i).y)    ymx= vp.get(i).y;
            }
            // 移動
            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));
            Trans.setTransform(t3d);
            // 縮小
            xrate= 1.0f/(xmx-xmn);
            yrate= 1.0f/(ymx-ymn);
            Transform3D scl = new Transform3D();
            if (xrate<yrate)    scl.setScale(xrate);
            else                scl.setScale(yrate);
            Size.setTransform(scl);
        }
    
        // 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;
        }
    
        // Next Float(BUF から次の値を取得)
        private float FVal()
        {   if (token.hasMoreTokens()==false)
                if (NextRead()==false)  return -1.0f;
            return Float.parseFloat(token.nextToken());
        }
    
        // Next int(BUF から次の値を取得)
        private int Val()
        {   if (token.hasMoreTokens()==false)
                if (NextRead()==false)  return -1;
            return Integer.parseInt(token.nextToken());
        }
    
        // 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. テクスチャを張り付けたXモデル(XFILE)を描画 から修正した箇所を重点的に説明します。
    Java Applet でも使えるようにクラス名を小文字で統一しました。
    私がサーバーにアップロードするときに、全てのファイル名を小文字に変換しているためです。
    また Java Applet から呼び出せるように、全ての load() メソッドをサポートしました。
    これで任意のフォルダーに格納されているモデルを直接指定することが出来ます。
        @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;
        }
        
  3. モデルのサイズが大きすぎたり、小さすぎたりするとプログラムは正しくても描画されないことがあります。
    そこでモデルを標準のサイズに縮小・拡大して、画面中央に配置する RESIZE パラメータをサポートしました。
            // モデルの入力
            x_loader f = new x_loader(x_loader.RESIZE);
            Scene s = null;
    
            s = f.load("C:\\DATA\\Model\\ROKU\\gal3v2.x");
            trans.addChild(s.getSceneGroup());
                ・・・
    
        public static final int RESIZE = 1;
        SceneBase       Base;          // SceneBase
        int             FLAG;          // Loader Flag
        TransformGroup  Rot;           // 回転
        TransformGroup  Trans;         // 中央移動
        TransformGroup  Size;          // サイズ調整
                ・・・
    
        // Constructor
        public x_loader()
        {   FLAG= 0;
        }
        public x_loader(int flag)
        {   FLAG= flag;
        }
        
  4. Create() メソッドで Trans と Size に初期値を設定して下さい。
            // 回転の初期化
            Rot = new TransformGroup();
            Transform3D rotate = new Transform3D();
            rotate.rotY(Math.PI);       // Y 軸について180度回転
            Rot.setTransform(rotate);
    
            // 移動の初期化
            Trans = new TransformGroup();
            Transform3D t3d = new Transform3D();
            t3d.setTranslation(new Vector3d(0.0, 0.0, 0.0));
            Trans.setTransform(t3d);
    
            // サイズの初期化
            Size = new TransformGroup();
            Transform3D scl = new Transform3D();
            scl.setScale(1.0);
            Size.setTransform(scl);
    
            Trans.addChild(Rot);
            Size.addChild(Trans);
            Base.getSceneGroup().addChild(Size);
    
            set_data();
            creatscene();
            if ((FLAG&1) == 1)  // RESIZE フラグ ON
            {   Resize();  }
        }
        
  5. Resize() メソッドでモデルの大きさを調べて、移動と拡大・縮小を設定します。
        // Model の Resize
        private void Resize()
        {   float   xmn,xmx,ymn,ymx,xrate,yrate;
            if (vp.size()==0)   return;
            xmn=xmx= vp.get(0).x;
            ymn=ymx= vp.get(0).y;
            for(int i=1; i<vp.size(); i++)
            {   if (xmn>vp.get(i).x)    xmn= vp.get(i).x;
                if (xmx<vp.get(i).x)    xmx= vp.get(i).x;
                if (ymn>vp.get(i).y)    ymn= vp.get(i).y;
                if (ymx<vp.get(i).y)    ymx= vp.get(i).y;
            }
            // 移動
            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));
            Trans.setTransform(t3d);
            // 縮小
            xrate= 1.0f/(xmx-xmn);
            yrate= 1.0f/(ymx-ymn);
            Transform3D scl = new Transform3D();
            if (xrate<yrate)    scl.setScale(xrate);
            else                scl.setScale(yrate);
            Size.setTransform(scl);
        }
        
  6. Shape3D は Trans に追加します。
        public void face(TransformGroup trans, Point3f[] vertices, Vector3f[] normal, mats mat, TexCoord2f[] texture)
        {
            ・・・
    
            // Material を設定して SceneBase(TransformGroup) に追加
            Shape3D shape = new Shape3D(ginfo.getGeometryArray());
            shape.setAppearance(createAppearance(mat));
            trans.addChild(shape);
        
  7. XFILE を入力してモデルを描画する main() プログラムは X-FILE Loader の基礎 を参照して下さい。
    ネットからダウンロードした六角大王のフリー版のモデルを XFILE に変換して描画してみました。
    今なら結構沢山のモデルがネットから無料でダウンロード出来ます。 (^o^)/
            // モデルの入力
            x_loader f = new x_loader(x_loader.RESIZE);
            Scene s = null;
    
            //s = f.load("c:\\DATA\\Java3D\\10XFile\\Cube.x");
            //s = f.load("c:\\DATA\\Java3D\\10XFile\\04Texture\\cube_texture2.x");
            //s = f.load("C:\\DATA\\Model\\ROKU\\eyg.x");
            //s = f.load("C:\\DATA\\Model\\ROKU\\WIZ01.x");
            //s = f.load("C:\\DATA\\Model\\ROKU\\gal3v2.x");
            s = f.load("C:\\DATA\\Model\\char.x");
        

【NOTE】

  1. Java でサポートする画像の形式です。
    bmp / wbmp / jpeg / png / gif
    DirectX(XFILE)では gif 形式は使えません。
  2. Java3D は右手座標系ですが DirectX は左手座標系なので、多少の違いが生じるかも知れません。
    座標系の説明は Windows Guid を参照して下さい。
  3. Java3D と XFILE ではテクスチャのY座標が上下逆になっていました。
    (Java では左下が座標 0,0 で、DirectX では左上が座標 0,0 になる)
  4. モデルやテクスチャのサイズが大きいと、メモリ不足で動かないかも知れません。
    私は開発用にメモリを増設しているのですが、次のエラーが表示されました。
    このメッセージだけでメモリ不足と断定するのは早計かも知れませんが。 (^_^;)
          Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
          at javax.media.j3d.ImageComponentRetained$ImageData.(ImageComponentRetained.java:2252)
        
    テクスチャ画像を縮小して描画することが出来ました。
    テクスチャは 0.0f〜1.0f の相対座標なので、画像を縮小してもモデルはそのまま使えます。

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