OSDN Git Service

TogaGemからのパッケージ移管。
authorOlyutorskii <olyutorskii@users.osdn.me>
Wed, 24 Aug 2011 11:42:39 +0000 (20:42 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Wed, 24 Aug 2011 11:42:39 +0000 (20:42 +0900)
40 files changed:
src/main/java/jp/sourceforge/mikutoga/pmd/model/BoneGroup.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/BoneInfo.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/DynamicsInfo.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/IKChain.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/JointInfo.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/ListUtil.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/Material.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/MorphPart.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/MorphVertex.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/PmdModel.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidGroup.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidInfo.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidShape.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/SerialNumbered.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/ShadeInfo.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/Surface.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/ToonMap.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/Vertex.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/BoneBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/JointBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/MaterialBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/MorphBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterBase.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt1.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt2.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt3.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/RigidBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/ShapeBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/TextBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/ToonBuilder.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/PmdXmlExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/PmdXmlResources.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/Xml2PmdLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/pmd2xml/Pmd2Xml.java
src/main/resources/jp/sourceforge/mikutoga/pmd/model/xml/resources/pmdxml-101009.dtd [new file with mode: 0644]
src/main/resources/jp/sourceforge/mikutoga/pmd/model/xml/resources/pmdxml-101009.xsd [new file with mode: 0644]

diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/BoneGroup.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/BoneGroup.java
new file mode 100644 (file)
index 0000000..6c598d2
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * bone group
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+
+/**
+ * ボーングループ。
+ * ボーングループ名と0個以上のボーンを配下に持つ。
+ * 通し番号0のボーングループは、暗黙に用意される「デフォルトボーングループ」とする。
+ */
+public class BoneGroup implements SerialNumbered , Iterable<BoneInfo> {
+
+    private final I18nText groupName = new I18nText();
+
+    private final List<BoneInfo> boneList = new ArrayList<BoneInfo>();
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public BoneGroup(){
+        super();
+        return;
+    }
+
+    /**
+     * ボーングループ名を返す。
+     * @return ボーングループ名
+     */
+    public I18nText getGroupName(){
+        return this.groupName;
+    }
+
+    /**
+     * ボーンリストを取得する。
+     * @return ボーンリスト
+     */
+    public List<BoneInfo> getBoneList(){
+        return this.boneList;
+    }
+
+    /**
+     * デフォルトボーングループか否か判定する。
+     * 通し番号が0ならデフォルトボーングループ。
+     * @return デフォルトボーングループならtrue
+     */
+    public boolean isDefaultBoneGroup(){
+        if(this.serialNo == 0) return true;
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Iterator<BoneInfo> iterator(){
+        return this.boneList.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * ボーングループ番号(ボーン枠番号)を返す。
+     * 常に通し番号より1少ない値となる。
+     * デフォルトボーングループは-1となる。
+     * @return ボーングループ番号
+     */
+    public int getBoneGroupNumber(){
+        return this.serialNo - 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("BoneGroup(")
+              .append(this.groupName)
+              .append(") [");
+
+        boolean dumped = false;
+        for(BoneInfo bone : this){
+            if(dumped) result.append(", ");
+            result.append(bone.getBoneName());
+            dumped = true;
+        }
+
+        result.append(']');
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/BoneInfo.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/BoneInfo.java
new file mode 100644 (file)
index 0000000..5f17397
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * bone information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+
+/**
+ * ボーン情報。
+ */
+public class BoneInfo implements SerialNumbered {
+
+    private final I18nText boneName = new I18nText();
+    private BoneType boneType;
+
+    private BoneInfo prevBone;
+    private BoneInfo nextBone;
+    private BoneInfo ikBone;
+
+    private final MkPos3D position = new MkPos3D();
+
+    private int rotationRatio;
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public BoneInfo(){
+        super();
+        return;
+    }
+
+    /**
+     * ボーン名を返す。
+     * @return ボーン名
+     */
+    public I18nText getBoneName(){
+        return this.boneName;
+    }
+
+    /**
+     * ボーン種別を設定する。
+     * @param type ボーン種別
+     * @throws NullPointerException 引数がnull
+     */
+    public void setBoneType(BoneType type) throws NullPointerException{
+        if(type == null) throw new NullPointerException();
+        this.boneType = type;
+        return;
+    }
+
+    /**
+     * ボーン種別を返す。
+     * @return ボーン種別
+     */
+    public BoneType getBoneType(){
+        return this.boneType;
+    }
+
+    /**
+     * 親(前)ボーンを設定する。
+     * @param prevBone 前ボーン。ない場合はnullを指定。
+     */
+    public void setPrevBone(BoneInfo prevBone){
+        this.prevBone = prevBone;
+        return;
+    }
+
+    /**
+     * 親(前)ボーンを返す。
+     * @return 前ボーン。ない場合はnullを返す。
+     */
+    public BoneInfo getPrevBone(){
+        return this.prevBone;
+    }
+
+    /**
+     * 子(次)ボーンを設定する。
+     * 捩りボーンでは軸方向に位置するボーン、
+     * 回転連動ボーンでは影響元ボーン。
+     * @param nextBone 次ボーン。ない場合はnullを指定。
+     */
+    public void setNextBone(BoneInfo nextBone){
+        this.nextBone = nextBone;
+        return;
+    }
+
+    /**
+     * 子(次)ボーンを返す。
+     * 捩りボーンでは軸方向に位置するボーン、
+     * 回転連動ボーンでは影響元ボーン。
+     * @return 次ボーン。ない場合はnullを返す。
+     */
+    public BoneInfo getNextBone(){
+        return this.nextBone;
+    }
+
+    /**
+     * このボーンが影響を受けるIKボーンを設定する。
+     * @param ikBoneArg IKボーン。ない場合はnullを指定。
+     */
+    public void setIKBone(BoneInfo ikBoneArg){
+        this.ikBone = ikBoneArg;
+        return;
+    }
+
+    /**
+     * このボーンが影響を受けるIKボーンを返す。
+     * @return IKボーン。ない場合はnull
+     */
+    public BoneInfo getIKBone(){
+        return this.ikBone;
+    }
+
+    /**
+     * ボーン位置を返す。
+     * @return ボーン位置
+     */
+    public MkPos3D getPosition(){
+        return this.position;
+    }
+
+    /**
+     * 回転連動の影響度を返す。
+     * 回転連動ボーンの場合のみ有効。
+     * @return 回転連動の影響度
+     */
+    public int getRotationRatio(){
+        return this.rotationRatio;
+    }
+
+    /**
+     * 回転連動の影響度を設定する。
+     * 回転連動ボーンの場合のみ有効。
+     * @param ratio 回転連動の影響度
+     */
+    public void setRotationRatio(int ratio){
+        this.rotationRatio = ratio;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("Bone")
+              .append(this.serialNo)
+              .append("(")
+              .append(this.boneName.getPrimaryText())
+              .append(")");
+
+        result.append(" type=")
+              .append(this.boneType);
+
+        result.append(" prev=");
+        if(this.prevBone != null) result.append(this.prevBone.getBoneName());
+        else                      result.append("NONE");
+
+        result.append(" next=");
+        if(this.nextBone != null) result.append(this.nextBone.getBoneName());
+        else                      result.append("NONE");
+
+        if(this.boneType == BoneType.LINKEDROT){
+            result.append(" rotraio=").append(this.rotationRatio);
+        }else{
+            result.append(" ik=");
+            if(this.ikBone != null) result.append(this.ikBone.getBoneName());
+            else                    result.append("NONE");
+        }
+
+        result.append(" ").append(this.position);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/DynamicsInfo.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/DynamicsInfo.java
new file mode 100644 (file)
index 0000000..6b6f68e
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * dynamics parameter
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+/**
+ * 剛体間力学演算の各種パラメータ。
+ * 各剛体に設定可能なパラメータは
+ * 「質量」、「移動減衰率」、「回転減衰率」、「反発力」、「摩擦力」の5種類。
+ */
+public class DynamicsInfo {
+
+    /** 質量。 */
+    private float mass;
+    /** 移動減衰率。 */
+    private float dampingPos;
+    /** 回転減衰率。 */
+    private float dampingRot;
+    /** 反発力。 */
+    private float restitution;
+    /** 摩擦力。 */
+    private float friction;
+
+    /**
+     * コンストラクタ。
+     */
+    public DynamicsInfo(){
+        super();
+        return;
+    }
+
+    /**
+     * 質量を返す。
+     * @return 質量
+     */
+    public float getMass(){
+        return this.mass;
+    }
+
+    /**
+     * 質量を設定する。
+     * @param mass 質量
+     */
+    public void setMass(float mass){
+        this.mass = mass;
+        return;
+    }
+
+    /**
+     * 移動減衰率を返す。
+     * @return 移動減衰率
+     */
+    public float getDampingPosition(){
+        return this.dampingPos;
+    }
+
+    /**
+     * 移動減衰率を設定する。
+     * @param damping 移動減衰率
+     */
+    public void setDampingPosition(float damping){
+        this.dampingPos = damping;
+        return;
+    }
+
+    /**
+     * 回転減衰率を返す。
+     * @return 回転減衰率
+     */
+    public float getDampingRotation(){
+        return this.dampingRot;
+    }
+
+    /**
+     * 回転減衰率を設定する。
+     * @param damping 回転減衰率
+     */
+    public void setDampingRotation(float damping){
+        this.dampingRot = damping;
+        return;
+    }
+
+    /**
+     * 反発力を返す。
+     * @return 反発力
+     */
+    public float getRestitution(){
+        return this.restitution;
+    }
+
+    /**
+     * 反発力を設定する。
+     * @param restitution 反発力
+     */
+    public void setRestitution(float restitution){
+        this.restitution = restitution;
+        return;
+    }
+
+    /**
+     * 摩擦力を返す。
+     * @return 摩擦力
+     */
+    public float getFriction(){
+        return this.friction;
+    }
+
+    /**
+     * 摩擦力を設定する。
+     * @param friction 摩擦力
+     */
+    public void setFriction(float friction){
+        this.friction = friction;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("mass=").append(this.mass).append(", ");
+        result.append("damping(Pos)=").append(this.dampingPos).append(", ");
+        result.append("damping(Rot)=").append(this.dampingRot).append(", ");
+        result.append("restitution=").append(this.restitution).append(", ");
+        result.append("friction=").append(this.friction);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/IKChain.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/IKChain.java
new file mode 100644 (file)
index 0000000..ece4d56
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * IK chained bone
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * IK連鎖ボーン群。
+ */
+public class IKChain implements Iterable<BoneInfo> {
+
+    private BoneInfo ikBone;
+
+    private int ikDepth;
+    private float ikWeight;
+
+    private final List<BoneInfo> chainList = new ArrayList<BoneInfo>();
+
+    /**
+     * コンストラクタ。
+     */
+    public IKChain(){
+        super();
+        return;
+    }
+
+    /**
+     * IKボーンを設定する。
+     * @param bone IKボーン
+     */
+    public void setIkBone(BoneInfo bone){
+        this.ikBone = bone;
+        return;
+    }
+
+    /**
+     * IKボーンを返す。
+     * @return IKボーン
+     */
+    public BoneInfo getIkBone(){
+        return this.ikBone;
+    }
+
+    /**
+     * IK演算再帰深度を設定する。
+     * @param depth IK演算再帰深度
+     */
+    public void setIKDepth(int depth){
+        this.ikDepth = depth;
+        return;
+    }
+
+    /**
+     * IK演算再帰深度を返す。
+     * @return IK演算再帰深度
+     */
+    public int getIKDepth(){
+        return this.ikDepth;
+    }
+
+    /**
+     * IKウェイトを設定する。
+     * @param weight IKウェイト
+     */
+    public void setIKWeight(float weight){
+        this.ikWeight = weight;
+        return;
+    }
+
+    /**
+     * IKウェイトを返す。
+     * @return IKウェイト
+     */
+    public float getIKWeight(){
+        return this.ikWeight;
+    }
+
+    /**
+     * IK連鎖ボーンリストを返す。
+     * 最初の要素は必ずIK接続先ボーン。それ以降はIK影響下ボーン。
+     * @return IK連鎖ボーンリスト
+     */
+    public List<BoneInfo> getChainedBoneList(){
+        return this.chainList;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Iterator<BoneInfo> iterator(){
+        return this.chainList.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("IKChain");
+
+        result.append(" depth:").append(this.ikDepth);
+        result.append(" weight:").append(this.ikWeight);
+
+        result.append(" IKbone:").append(this.ikBone.getBoneName());
+
+        result.append(" [");
+
+        boolean chaindumped = false;
+        for(BoneInfo chain : this.chainList){
+            if(chaindumped) result.append(" => ");
+            result.append(chain.getBoneName());
+            chaindumped = true;
+        }
+
+        result.append("]");
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/JointInfo.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/JointInfo.java
new file mode 100644 (file)
index 0000000..38967ae
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * joint information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.pmd.Deg3d;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.TripletRange;
+
+/**
+ * 剛体間ジョイント情報。
+ */
+public class JointInfo {
+
+    private final I18nText jointName = new I18nText();
+
+    private RigidInfo rigidA;
+    private RigidInfo rigidB;
+
+    private final MkPos3D position = new MkPos3D();
+    private final Rad3d rotation = new Rad3d();
+
+    private final MkPos3D elaPosition = new MkPos3D();
+    private final Deg3d elaRotation = new Deg3d();
+
+    private final TripletRange posRange = new TripletRange();
+    private final TripletRange rotRange = new TripletRange();
+
+    /**
+     * コンストラクタ。
+     */
+    public JointInfo(){
+        super();
+        return;
+    }
+
+    /**
+     * ジョイント名を返す。
+     * @return ジョイント名
+     */
+    public I18nText getJointName(){
+        return this.jointName;
+    }
+
+    /**
+     * 連結剛体Aを返す。
+     * @return 連結剛体A
+     */
+    public RigidInfo getRigidA(){
+        return this.rigidA;
+    }
+
+    /**
+     * 連結剛体Bを返す。
+     * @return 連結剛体B
+     */
+    public RigidInfo getRigidB(){
+        return this.rigidB;
+    }
+
+    /**
+     * 連結する剛体を設定する。
+     * @param rigidA 連結剛体A
+     * @param rigidB 連結剛体B
+     */
+    public void setRigidPair(RigidInfo rigidA, RigidInfo rigidB){
+        this.rigidA = rigidA;
+        this.rigidB = rigidB;
+        return;
+    }
+
+    /**
+     * ジョイントの位置を返す。
+     * @return ジョイントの位置
+     */
+    public MkPos3D getPosition(){
+        return this.position;
+    }
+
+    /**
+     * ジョイントの姿勢を返す。
+     * @return ジョイントの姿勢
+     */
+    public Rad3d getRotation(){
+        return this.rotation;
+    }
+
+    /**
+     * ジョイントのバネ位置を返す。
+     * @return ジョイントのバネ位置
+     */
+    public MkPos3D getElasticPosition(){
+        return this.elaPosition;
+    }
+
+    /**
+     * ジョイントのバネ姿勢を返す。
+     * @return ジョイントのバネ姿勢
+     */
+    public Deg3d getElasticRotation(){
+        return this.elaRotation;
+    }
+
+    /**
+     * ジョイントの位置制約を返す。
+     * @return ジョイントの位置制約
+     */
+    public TripletRange getPositionRange(){
+        return this.posRange;
+    }
+
+    /**
+     * ジョイントの姿勢制約を返す。
+     * @return ジョイントの姿勢制約
+     */
+    public TripletRange getRotationRange(){
+        return this.rotRange;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("Joint ");
+        result.append(this.jointName);
+        result.append("[")
+              .append(this.rigidA.getRigidName())
+              .append("<=>")
+              .append(this.rigidB.getRigidName())
+              .append("] ");
+        result.append(this.position).append(' ');
+        result.append(this.rotation).append(' ');
+
+        result.append("poslim{").append(this.posRange).append("} ");
+        result.append("rotlim{").append(this.rotRange).append("} ");
+
+        result.append("ela:").append(this.elaPosition).append(' ');
+        result.append("ela:").append(this.elaRotation);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/ListUtil.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/ListUtil.java
new file mode 100644 (file)
index 0000000..0cc79cb
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * List utilities
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ * リスト構造の各種ユーティリティ。
+ */
+public final class ListUtil {
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private ListUtil(){
+        assert false;
+        throw new AssertionError();
+    }
+
+    /**
+     * リストの出現順にシリアルナンバーを割り振る。
+     * シリアルナンバー先頭は0。
+     * @param list リスト。
+     */
+    public static void assignIndexedSerial(
+            List<? extends SerialNumbered> list){
+        int serial = 0;
+        for(SerialNumbered numbered : list){
+            numbered.setSerialNumber(serial);
+            serial++;
+        }
+
+        return;
+    }
+
+    /**
+     * リストの要素数を拡張する。
+     * 追加された要素にはnullが収められる。
+     * リストがすでに指定サイズ以上の要素数を持つ場合、何もしない。
+     * @param <E> 型
+     * @param list リスト
+     * @param newSize 新サイズ
+     */
+    public static <E> void extendList(List<E> list, int newSize){
+        int remain = newSize - list.size();
+        if(remain <= 0) return;
+
+        for(int ct = 1; ct <= remain; ct++){
+            list.add(null);
+        }
+
+        assert list.size() == newSize;
+
+        return;
+    }
+
+    /**
+     * リストのnull要素をデフォルトコンストラクタによるインスタンスで埋める。
+     * null要素がなければなにもしない。
+     * @param <E> 型
+     * @param list リスト
+     * @param cons コンストラクタ
+     * @return 埋めた数
+     */
+    public static <E> int fillDefCons(List<E> list,
+                                      Constructor<? extends E> cons){
+        int result = 0;
+
+        int size = list.size();
+        for(int pt = 0; pt < size; pt++){
+            E elem = list.get(pt);
+            if(elem != null) continue;
+
+            try{
+                elem = cons.newInstance();
+            }catch(InstantiationException e){
+                assert false;
+                throw new AssertionError(e);
+            }catch(IllegalAccessException e){
+                assert false;
+                throw new AssertionError(e);
+            }catch(InvocationTargetException e){
+                Throwable cause = e.getTargetException();
+                if(cause instanceof RuntimeException){
+                    throw (RuntimeException) cause;
+                }
+                if(cause instanceof Error){
+                    throw (Error) cause;
+                }
+                assert false;
+                throw new AssertionError(e);
+            }
+
+            list.set(pt, elem);
+            result++;
+        }
+
+        return result;
+    }
+
+    /**
+     * リストのnull要素をデフォルトコンストラクタによるインスタンスで埋める。
+     * @param <E> 型
+     * @param list リスト
+     * @param klass デフォルトコンストラクタの属するクラス
+     * @return 埋めた数
+     */
+    public static <E> int fillDefCons(List<E> list, Class<? extends E> klass){
+        Constructor<? extends E> cons;
+        try{
+            cons = klass.getConstructor();
+        }catch(NoSuchMethodException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        return fillDefCons(list, cons);
+    }
+
+    /**
+     * リスト要素数の拡張とデフォルトコンストラクタによる要素埋めを行う。
+     * 追加された要素および既存のnull要素には
+     * デフォルトコンストラクタによるインスタンスが収められる。
+     * @param <E> 型
+     * @param list リスト
+     * @param klass デフォルトコンストラクタの属するクラス
+     * @param newSize 新サイズ
+     * @return 埋めた数。
+     */
+    public static <E> int prepareDefConsList(List<E> list,
+                                             Class<? extends E> klass,
+                                             int newSize ){
+        extendList(list, newSize);
+        int result = fillDefCons(list, klass);
+        return result;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/Material.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/Material.java
new file mode 100644 (file)
index 0000000..4431227
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * material information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.awt.Color;
+import java.awt.Transparency;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+
+/**
+ * マテリアル素材情報。
+ */
+public class Material implements Iterable<Surface> {
+
+    private final I18nText materialName = new I18nText();
+
+    private Color diffuseColor;
+
+    private Color specularColor;
+    private float shininess;
+
+    private Color ambientColor;
+
+    private final ShadeInfo shadeInfo = new ShadeInfo();
+
+    private boolean edgeAppearance = true;
+
+    private final List<Surface> surfaceList = new ArrayList<Surface>();
+
+    /**
+     * コンストラクタ。
+     */
+    public Material(){
+        super();
+        return;
+    }
+
+    /**
+     * 色を不透明化する。
+     * @param color 色
+     * @return 不透明化した色。引数と同じ場合もありうる。
+     */
+    private static Color forceOpaque(Color color){
+        if(color.getTransparency() == Transparency.OPAQUE){
+            return color;
+        }
+
+        float[] rgba = new float[4];
+        color.getRGBColorComponents(rgba);
+
+        Color result = new Color(rgba[0], rgba[1], rgba[2], 1.0f);
+
+        return result;
+    }
+
+    /**
+     * マテリアル名を返す。
+     * PMDEditorのみでのサポート?
+     * @return マテリアル名
+     */
+    public I18nText getMaterialName(){
+        return this.materialName;
+    }
+
+    /**
+     * 拡散光を設定する。
+     * アルファ成分も反映される。
+     * @param color 拡散光
+     * @throws NullPointerException 引数がnull
+     */
+    public void setDiffuseColor(Color color) throws NullPointerException{
+        if(color == null) throw new NullPointerException();
+        this.diffuseColor = color;
+        return;
+    }
+
+    /**
+     * 拡散光を返す。
+     * @return 拡散光
+     */
+    public Color getDiffuseColor(){
+        return this.diffuseColor;
+    }
+
+    /**
+     * 反射光を設定する。
+     * 透過成分があれば不透明化される。
+     * @param color 反射光
+     * @throws NullPointerException 引数がnull
+     */
+    public void setSpecularColor(Color color)
+            throws NullPointerException{
+        if(color == null) throw new NullPointerException();
+        this.specularColor = forceOpaque(color);
+        return;
+    }
+
+    /**
+     * 反射光を返す。
+     * @return 反射光
+     */
+    public Color getSpecularColor(){
+        return this.specularColor;
+    }
+
+    /**
+     * 環境光を設定する。
+     * 透過成分があれば不透明化される。
+     * @param color 環境光
+     * @throws NullPointerException 引数がnull
+     */
+    public void setAmbientColor(Color color)
+            throws NullPointerException{
+        if(color == null) throw new NullPointerException();
+        this.ambientColor = forceOpaque(color);
+        return;
+    }
+
+    /**
+     * 環境光を返す。
+     * @return 環境光
+     */
+    public Color getAmbientColor(){
+        return this.ambientColor;
+    }
+
+    /**
+     * 光沢強度を設定する。
+     * MMDで用いられるのは1.0〜15.0あたり。
+     * @param shininess 光沢強度
+     */
+    public void setShininess(float shininess){
+        this.shininess = shininess;
+        return;
+    }
+
+    /**
+     * 光沢強度を返す。
+     * @return 光沢強度
+     */
+    public float getShininess(){
+        return this.shininess;
+    }
+
+    /**
+     * シェーディング設定を返す。
+     * @return シェーディング設定
+     */
+    public ShadeInfo getShadeInfo(){
+        return this.shadeInfo;
+    }
+
+    /**
+     * エッジを表示するか設定する。
+     * 頂点単位の設定より優先度は低い。
+     * @param show 表示するならtrue
+     */
+    public void setEdgeAppearance(boolean show){
+        this.edgeAppearance = show;
+        return;
+    }
+
+    /**
+     * エッジを表示するか判定する。
+     * 頂点単位の設定より優先度は低い。
+     * @return 表示するならtrue
+     */
+    public boolean getEdgeAppearance(){
+        return this.edgeAppearance;
+    }
+
+    /**
+     * 面リストを返す。
+     * @return 面リスト
+     */
+    public List<Surface> getSurfaceList(){
+        return this.surfaceList;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Iterator<Surface> iterator(){
+        return this.surfaceList.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("Material ");
+
+        float[] rgba = new float[4];
+
+        this.diffuseColor.getRGBComponents(rgba);
+        result.append("diffuse=[")
+              .append(rgba[0]).append(", ")
+              .append(rgba[1]).append(", ")
+              .append(rgba[2]).append(", ")
+              .append(rgba[3]).append(']')
+              .append(' ');
+
+        this.specularColor.getRGBComponents(rgba);
+        result.append("specular=[")
+              .append(rgba[0]).append(", ")
+              .append(rgba[1]).append(", ")
+              .append(rgba[2]).append(", ")
+              .append(rgba[3]).append(']')
+              .append(' ');
+
+        this.ambientColor.getRGBComponents(rgba);
+        result.append("ambient=[")
+              .append(rgba[0]).append(", ")
+              .append(rgba[1]).append(", ")
+              .append(rgba[2]).append(", ")
+              .append(rgba[3]).append(']')
+              .append(' ');
+
+        result.append("shininess=")
+              .append(this.shininess)
+              .append(' ');
+        result.append("edge=")
+              .append(this.edgeAppearance)
+              .append(' ');
+        result.append("surfaces=")
+              .append(this.surfaceList.size())
+              .append(' ');
+        result.append(this.shadeInfo);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/MorphPart.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/MorphPart.java
new file mode 100644 (file)
index 0000000..6e6e26c
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * morph information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+
+/**
+ * 個別モーフ情報。
+ */
+public class MorphPart implements SerialNumbered, Iterable<MorphVertex> {
+
+    private final I18nText morphName = new I18nText();
+
+    private MorphType type;
+
+    private final List<MorphVertex> morphVertexList =
+            new ArrayList<MorphVertex>();
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public MorphPart(){
+        super();
+        return;
+    }
+
+    /**
+     * モーフ名を返す。
+     * @return モーフ名
+     */
+    public I18nText getMorphName(){
+        return this.morphName;
+    }
+
+    /**
+     * モーフ種別を設定する。
+     * @param typeArg モーフ種別
+     * @throws NullPointerException 引数がnull
+     */
+    public void setMorphType(MorphType typeArg) throws NullPointerException{
+        if(typeArg == null) throw new NullPointerException();
+        this.type = typeArg;
+        return;
+    }
+
+    /**
+     * モーフ種別を返す。
+     * @return モーフ種別。
+     */
+    public MorphType getMorphType(){
+        return this.type;
+    }
+
+    /**
+     * モーフ頂点情報リストを返す。
+     * @return モーフ頂点情報リスト
+     */
+    public List<MorphVertex> getMorphVertexList(){
+        return this.morphVertexList;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Iterator<MorphVertex> iterator(){
+        return this.morphVertexList.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("Morph(").append(this.morphName).append(") ");
+        result.append("type=").append(this.type);
+        result.append(" vertexNum=").append(this.morphVertexList.size());
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/MorphVertex.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/MorphVertex.java
new file mode 100644 (file)
index 0000000..1fd04b7
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * morph vertex information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.Comparator;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+
+/**
+ * モーフアニメーションを構成する個別の頂点移動の情報。
+ */
+public class MorphVertex implements SerialNumbered{
+
+    /** 頂点IDを昇順に順序づけるComaparator。 */
+    public static final Comparator<MorphVertex> VIDCOMPARATOR =
+            new VertexIdComparator();
+
+    private Vertex baseVertex;
+    private final MkPos3D offset = new MkPos3D();
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public MorphVertex(){
+        super();
+        return;
+    }
+
+    /**
+     * 移動元頂点情報を返す。
+     * @return 移動元頂点
+     */
+    public Vertex getBaseVertex(){
+        return this.baseVertex;
+    }
+
+    /**
+     * 移動元頂点情報を設定する。
+     * @param vertex 移動元頂点
+     * @throws NullPointerException 引数がnull
+     */
+    public void setBaseVertex(Vertex vertex) throws NullPointerException{
+        if(vertex == null) throw new NullPointerException();
+        this.baseVertex = vertex;
+        return;
+    }
+
+    /**
+     * 頂点移動量を返す。
+     * @return 頂点移動量
+     */
+    public MkPos3D getOffset(){
+        return this.offset;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("vid(")
+              .append(this.baseVertex.getSerialNumber())
+              .append(") ");
+        result.append(this.baseVertex.getPosition());
+        result.append(" >> ");
+        result.append(this.offset);
+
+        return result.toString();
+    }
+
+    /**
+     * 頂点IDによる比較子Comparator。
+     */
+    private static final class VertexIdComparator
+            implements Comparator<MorphVertex> {
+
+        /**
+         * コンストラクタ。
+         */
+        private VertexIdComparator(){
+            super();
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param o1 {@inheritDoc}
+         * @param o2 {@inheritDoc}
+         * @return {@inheritDoc}
+         */
+        @Override
+        public int compare(MorphVertex o1, MorphVertex o2){
+            if(o1 == o2) return 0;
+            if(o1 == null) return -1;
+            if(o2 == null) return +1;
+
+            int ser1 = o1.getBaseVertex().getSerialNumber();
+            int ser2 = o2.getBaseVertex().getSerialNumber();
+
+            return ser1 - ser2;
+        }
+
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/PmdModel.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/PmdModel.java
new file mode 100644 (file)
index 0000000..bfa1b34
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * PMD model
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.RandomAccess;
+import java.util.Set;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+
+/**
+ * PMDモデルファイル一式に相当するもの。
+ * 様々な基本構造のリストの集合から構成される。
+ */
+public class PmdModel {
+
+    private final I18nText modelName = new I18nText();
+    private final I18nText description = new I18nText();
+
+    private final List<Vertex> vertexList = new ArrayList<Vertex>();
+    private final List<Surface> surfaceList = new ArrayList<Surface>();
+    private final List<Material> materialList = new LinkedList<Material>();
+
+    private final List<BoneInfo> boneList = new ArrayList<BoneInfo>();
+    private final List<BoneGroup> boneGroupList = new ArrayList<BoneGroup>();
+
+    private final List<IKChain> ikChainList = new ArrayList<IKChain>();
+
+    private final Map<MorphType, List<MorphPart>> morphMap =
+            new EnumMap<MorphType, List<MorphPart>>(MorphType.class);
+
+    private final List<RigidInfo> rigidList = new ArrayList<RigidInfo>();
+    private final List<RigidGroup> rigidGroupList =
+            new ArrayList<RigidGroup>();
+
+    private final List<JointInfo> jointList = new ArrayList<JointInfo>();
+
+    private ToonMap toonMap = new ToonMap();
+
+    /**
+     * コンストラクタ。
+     */
+    public PmdModel(){
+        super();
+
+        assert this.vertexList instanceof RandomAccess;
+        assert this.surfaceList instanceof RandomAccess;
+
+        this.morphMap.put(MorphType.EYEBROW, new ArrayList<MorphPart>());
+        this.morphMap.put(MorphType.EYE,     new ArrayList<MorphPart>());
+        this.morphMap.put(MorphType.LIP,     new ArrayList<MorphPart>());
+        this.morphMap.put(MorphType.EXTRA,   new ArrayList<MorphPart>());
+
+        return;
+    }
+
+    /**
+     * モデル名を返す。
+     * @return モデル名
+     */
+    public I18nText getModelName(){
+        return this.modelName;
+    }
+
+    /**
+     * モデル説明文を返す。
+     * 改行表現には{@literal \n}が用いられる
+     * @return モデル説明文
+     */
+    public I18nText getDescription(){
+        return this.description;
+    }
+
+    /**
+     * 頂点リストを返す。
+     * @return 頂点リスト。
+     */
+    public List<Vertex> getVertexList(){
+        return this.vertexList;
+    }
+
+    /**
+     * 面リストを返す。
+     * @return 面リスト
+     */
+    public List<Surface> getSurfaceList(){
+        return this.surfaceList;
+    }
+
+    /**
+     * 素材リストを返す。
+     * @return 素材リスト
+     */
+    public List<Material> getMaterialList(){
+        return this.materialList;
+    }
+
+    /**
+     * ボーンリストを返す。
+     * @return ボーンリスト
+     */
+    public List<BoneInfo> getBoneList(){
+        return this.boneList;
+    }
+
+    /**
+     * ボーングループリストを返す。
+     * @return ボーングループリスト
+     */
+    public List<BoneGroup> getBoneGroupList(){
+        return this.boneGroupList;
+    }
+
+    /**
+     * IKチェーンリストを返す。
+     * @return IKチェーンリスト
+     */
+    public List<IKChain> getIKChainList(){
+        return this.ikChainList;
+    }
+
+    /**
+     * 種類別モーフリストのマップを返す。
+     * @return 種類別モーフリストのマップ
+     */
+    public Map<MorphType, List<MorphPart>> getMorphMap(){
+        return this.morphMap;
+    }
+
+    /**
+     * 剛体リストを返す。
+     * @return 剛体リスト
+     */
+    public List<RigidInfo> getRigidList(){
+        return this.rigidList;
+    }
+
+    /**
+     * 剛体グループリストを返す。
+     * @return 剛体グループリスト。
+     */
+    public List<RigidGroup> getRigidGroupList(){
+        return this.rigidGroupList;
+    }
+
+    /**
+     * 剛体間ジョイントリストを返す。
+     * @return 剛体間ジョイントリスト
+     */
+    public List<JointInfo> getJointList(){
+        return this.jointList;
+    }
+
+    /**
+     * トゥーンファイルマップを返す。
+     * @return トゥーンファイルマップ
+     */
+    public ToonMap getToonMap(){
+        return this.toonMap;
+    }
+
+    /**
+     * トゥーンファイルマップを設定する。
+     * 各素材のシェーディングで参照するトゥーンファイルマップも更新される。
+     * @param map トゥーンファイルマップ
+     */
+    public void setToonMap(ToonMap map){
+        this.toonMap = map;
+        for(Material material : this.materialList){
+            ShadeInfo info = material.getShadeInfo();
+            info.setToonMap(this.toonMap);
+        }
+        return;
+    }
+
+    /**
+     * このモデルがグローバル名を含むか判定する。
+     * ボーン名、ボーングループ名、モーフ名、モデル説明文が判定対象。
+     * @return グローバル名を持つならtrue
+     */
+    public boolean hasGlobalText(){
+        if(this.modelName.hasGlobalText()) return true;
+        if(this.description.hasGlobalText()) return true;
+
+        for(BoneInfo bone : this.boneList){
+            if(bone.getBoneName().hasGlobalText()) return true;
+        }
+
+        List<MorphType> typeList = new ArrayList<MorphType>();
+        typeList.addAll(this.morphMap.keySet());
+        for(MorphType type : typeList){
+            List<MorphPart> partList = this.morphMap.get(type);
+            for(MorphPart part : partList){
+                if(part.getMorphName().hasGlobalText()) return true;
+            }
+        }
+
+        for(BoneGroup group : this.boneGroupList){
+            if(group.getGroupName().hasGlobalText()) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * モーフで使われる全てのモーフ頂点のリストを返す。
+     * モーフ間で重複する頂点はマージされる。
+     * 頂点IDでソートされる。
+     * <p>
+     * 0から始まる通し番号がリナンバリングされる。
+     * 通し番号は返されるモーフ頂点リストの添え字番号と一致する。
+     * @return モーフに使われるモーフ頂点のリスト
+     */
+    public List<MorphVertex> mergeMorphVertex(){
+        List<MorphVertex> result = new ArrayList<MorphVertex>();
+
+        Set<Vertex> mergedVertexSet = new HashSet<Vertex>();
+        for(MorphType type : this.morphMap.keySet()){
+            if(type.isBase()) continue;
+            List<MorphPart> partList = this.morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                for(MorphVertex morphVertex : part){
+                    Vertex vertex = morphVertex.getBaseVertex();
+                    if(mergedVertexSet.contains(vertex)) continue;
+                    mergedVertexSet.add(vertex);
+                    result.add(morphVertex);
+                }
+            }
+        }
+
+        Collections.sort(result, MorphVertex.VIDCOMPARATOR);
+        for(int idx = 0; idx < result.size(); idx++){
+            MorphVertex morphVertex = result.get(idx);
+            morphVertex.setSerialNumber(idx);
+        }
+
+        Map<Vertex, MorphVertex> numberedMap =
+                new HashMap<Vertex, MorphVertex>();
+        for(MorphVertex morphVertex : result){
+            numberedMap.put(morphVertex.getBaseVertex(), morphVertex);
+        }
+
+        for(MorphType type : this.morphMap.keySet()){
+            if(type.isBase()) continue;
+            List<MorphPart> partList = this.morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                for(MorphVertex morphVertex : part){
+                    Vertex vertex = morphVertex.getBaseVertex();
+                    MorphVertex numbered = numberedMap.get(vertex);
+                    assert numbered != null;
+                    morphVertex.setSerialNumber(numbered.getSerialNumber());
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 永続化可能な状態へトリミングする。
+     * 各種オブジェクトの通し番号が変化する可能性がある。
+     */
+    public void trimming(){
+        List<Surface> trimmedSurfaceList = trimmingSurfaceList();
+        this.surfaceList.clear();
+        this.surfaceList.addAll(trimmedSurfaceList);
+
+        List<Vertex> trimmedVertexList = trimmingVertexList();
+        this.vertexList.clear();
+        this.vertexList.addAll(trimmedVertexList);
+
+        return;
+    }
+
+    /**
+     * 面リストをトリミングする。
+     * 所属マテリアル順に再配置し、通し番号を割り振り直す。
+     * 所属マテリアルの無い面はリストの末端に配置される。
+     * 面リスト中のnullは削除され詰められる。
+     * @return トリミングされた面リスト
+     */
+    private List<Surface> trimmingSurfaceList(){
+        Set<Surface> materialedSurfaceSet = new HashSet<Surface>();
+        for(Material material : this.materialList){
+            if(material == null) continue;
+            for(Surface surface : material){
+                if(surface == null) continue;
+                materialedSurfaceSet.add(surface);
+            }
+        }
+
+        materialedSurfaceSet.removeAll(this.surfaceList);
+
+        List<Surface> result = new ArrayList<Surface>();
+        for(Surface surface : this.surfaceList){
+            if(surface == null) continue;
+            result.add(surface);
+        }
+
+        result.addAll(materialedSurfaceSet);
+
+        int serialNum = 0;
+        for(Surface surface : result){
+            surface.setSerialNumber(serialNum);
+            serialNum++;
+        }
+
+        return result;
+    }
+
+    /**
+     * 頂点リストをトリミングする。
+     * 通し番号を振り直す。
+     * 所属面の無い頂点はリストの末端に配置される。
+     * 頂点リスト中のnullは削除され詰められる。
+     * @return トリミングされた頂点リスト
+     */
+    private List<Vertex> trimmingVertexList(){
+        Set<Vertex> surfacedVertexSet = new HashSet<Vertex>();
+        for(Surface surface : this.surfaceList){
+            if(surface == null) continue;
+            for(Vertex vertex : surface){
+                surfacedVertexSet.add(vertex);
+            }
+        }
+
+        surfacedVertexSet.removeAll(this.vertexList);
+
+        List<Vertex> result = new ArrayList<Vertex>();
+        for(Vertex vertex : this.vertexList){
+            if(vertex == null) continue;
+            result.add(vertex);
+        }
+
+        result.addAll(surfacedVertexSet);
+
+        int serialNum = 0;
+        for(Vertex vertex : result){
+            vertex.setSerialNumber(serialNum);
+            serialNum++;
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidGroup.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidGroup.java
new file mode 100644 (file)
index 0000000..266964c
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * rigid group
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 剛体グループ情報。
+ * 剛体間の衝突設定の対象となる。
+ */
+public class RigidGroup implements SerialNumbered, Iterable<RigidInfo> {
+
+    private final List<RigidInfo> rigidList = new ArrayList<RigidInfo>();
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public RigidGroup(){
+        super();
+        return;
+    }
+
+    /**
+     * 所属する剛体のリストを返す。
+     * @return 剛体リスト
+     */
+    public List<RigidInfo> getRigidList(){
+        return this.rigidList;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Iterator<RigidInfo> iterator(){
+        return this.rigidList.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * グループ番号を返す。
+     * MMDでは1〜16までが使われる。
+     * 通し番号に1を加えた値と等しい。
+     * @return グループ番号
+     */
+    public int getGroupNumber(){
+        return this.serialNo + 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("RigidGroup(").append(getGroupNumber()).append(") [");
+
+        boolean dumped;
+
+        dumped = false;
+        for(RigidInfo rigid : this.rigidList){
+            if(dumped) result.append(", ");
+            result.append(rigid.getRigidName());
+            dumped = true;
+        }
+        result.append(']');
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidInfo.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidInfo.java
new file mode 100644 (file)
index 0000000..ff5bd34
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * rigid information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.RigidBehaviorType;
+
+/**
+ * 個別の剛体の情報。
+ */
+public class RigidInfo implements SerialNumbered {
+
+    private final I18nText rigidName = new I18nText();
+
+    private RigidBehaviorType behaviorType = RigidBehaviorType.FOLLOWBONE;
+
+    private final RigidShape rigidShape = new RigidShape();
+    private final MkPos3D position = new MkPos3D();
+    private final Rad3d rotation = new Rad3d();
+
+    private BoneInfo linkedBone;
+
+    private final DynamicsInfo dynamicsInfo = new DynamicsInfo();
+
+    private final Collection<RigidGroup> throughGroupColl =
+            new ArrayList<RigidGroup>();
+
+    private RigidGroup rigidGroup;
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public RigidInfo(){
+        super();
+        return;
+    }
+
+    /**
+     * 剛体名を返す。
+     * @return 剛体名
+     */
+    public I18nText getRigidName(){
+        return this.rigidName;
+    }
+
+    /**
+     * 剛体の振る舞い種別を返す。
+     * @return 剛体の振る舞い種別
+     */
+    public RigidBehaviorType getBehaviorType(){
+        return this.behaviorType;
+    }
+
+    /**
+     * 剛体の振る舞い種別を設定する。
+     * @param type 剛体の振る舞い種別。
+     * @throws NullPointerException 引数がnull
+     */
+    public void setBehaviorType(RigidBehaviorType type)
+            throws NullPointerException{
+        if(type == null) throw new NullPointerException();
+        this.behaviorType = type;
+        return;
+    }
+
+    /**
+     * 剛体形状を返す。
+     * @return 剛体形状
+     */
+    public RigidShape getRigidShape(){
+        return this.rigidShape;
+    }
+
+    /**
+     * 剛体位置を返す。
+     * @return 剛体位置
+     */
+    public MkPos3D getPosition(){
+        return this.position;
+    }
+
+    /**
+     * 剛体姿勢を返す。
+     * @return 剛体姿勢
+     */
+    public Rad3d getRotation(){
+        return this.rotation;
+    }
+
+    /**
+     * 接続ボーンを返す。
+     * @return 接続ボーン
+     */
+    public BoneInfo getLinkedBone(){
+        return this.linkedBone;
+    }
+
+    /**
+     * 接続ボーンを設定する。
+     * @param bone 接続ボーン
+     */
+    public void setLinkedBone(BoneInfo bone){
+        this.linkedBone = bone;
+        return;
+    }
+
+    /**
+     * 剛体の力学パラメータを返す。
+     * @return 力学パラメータ
+     */
+    public DynamicsInfo getDynamicsInfo(){
+        return this.dynamicsInfo;
+    }
+
+    /**
+     * 非衝突グループ集合を返す。
+     * @return 非衝突グループ集合
+     */
+    public Collection<RigidGroup> getThroughGroupColl(){
+        return this.throughGroupColl;
+    }
+
+    /**
+     * 所属する剛体グループを返す。
+     * @return 所属する剛体グループ
+     */
+    public RigidGroup getRigidGroup(){
+        return this.rigidGroup;
+    }
+
+    /**
+     * 所属する剛体グループを設定する。
+     * @param group 所属する剛体グループ
+     */
+    public void setRigidGroup(RigidGroup group){
+        this.rigidGroup = group;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        String boneName;
+        if(this.linkedBone == null){
+            boneName = "NOBONE";
+        }else{
+            boneName = this.linkedBone.getBoneName().toString();
+        }
+
+        result.append("Rigid(").append(this.rigidName).append(") ");
+        result.append("[=>")
+              .append(boneName)
+              .append("bone]");
+        result.append(" [").append(this.rigidShape).append("]");
+        result.append(" ").append(this.position);
+        result.append(" ").append(this.rotation);
+        result.append(" [").append(this.dynamicsInfo).append("]");
+        result.append("  [").append(this.behaviorType).append("]");
+
+        result.append(" through[");
+        boolean dumped = false;
+        for(RigidGroup group : this.throughGroupColl){
+            if(dumped) result.append(" ");
+            result.append(group.getGroupNumber());
+            dumped = true;
+        }
+        result.append("]");
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidShape.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/RigidShape.java
new file mode 100644 (file)
index 0000000..7a4773d
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * rigid shape information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import jp.sourceforge.mikutoga.pmd.RigidShapeType;
+
+/**
+ * 剛体形状に関する情報。
+ * 球及びカプセルの半径と箱の幅は同じ値が用いられる。
+ */
+public class RigidShape {
+
+    private RigidShapeType type = RigidShapeType.BOX;
+    private float width  = 0.1f;
+    private float height = 0.1f;
+    private float depth  = 0.1f;
+
+    /**
+     * コンストラクタ。
+     */
+    public RigidShape(){
+        super();
+        return;
+    }
+
+    /**
+     * 剛体形状種別を返す。
+     * @return 剛体形状種別
+     */
+    public RigidShapeType getShapeType(){
+        return this.type;
+    }
+
+    /**
+     * 剛体形状種別を設定する。
+     * @param typeArg 剛体形状種別
+     * @throws NullPointerException 引数がnull
+     */
+    public void setShapeType(RigidShapeType typeArg)
+            throws NullPointerException{
+        if(typeArg == null) throw new NullPointerException();
+        this.type = typeArg;
+        return;
+    }
+
+    /**
+     * 箱の幅を返す。
+     * @return 箱の幅
+     */
+    public float getWidth(){
+        return this.width;
+    }
+
+    /**
+     * 箱の幅を設定する。
+     * @param width 箱の幅
+     */
+    public void setWidth(float width){
+        this.width = width;
+        return;
+    }
+
+    /**
+     * 箱及びカプセルの高さを返す。
+     * @return 箱及びカプセルの高さ
+     */
+    public float getHeight(){
+        return this.height;
+    }
+
+    /**
+     * 箱及びカプセルの高さを設定する。
+     * @param height 箱及びカプセルの高さ
+     */
+    public void setHeight(float height){
+        this.height = height;
+        return;
+    }
+
+    /**
+     * 箱の奥行きを返す。
+     * @return 箱の奥行き
+     */
+    public float getDepth(){
+        return this.depth;
+    }
+
+    /**
+     * 箱の奥行きを設定する。
+     * @param depth 箱の奥行き
+     */
+    public void setDepth(float depth){
+        this.depth = depth;
+        return;
+    }
+
+    /**
+     * 球及びカプセルの半径を返す。
+     * @return 球及びカプセルの半径
+     */
+    public float getRadius(){
+        return this.width;
+    }
+
+    /**
+     * 球及びカプセルの半径を設定する。
+     * @param radius 球及びカプセルの半径
+     */
+    public void setRadius(float radius){
+        this.width = radius;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append(this.type).append(' ');
+
+        switch(this.type){
+        case SPHERE:
+            result.append("r=").append(this.width);
+            break;
+        case BOX:
+            result.append("w=").append(this.width).append(", ");
+            result.append("h=").append(this.height).append(", ");
+            result.append("d=").append(this.depth);
+            break;
+        case CAPSULE:
+            result.append("r=").append(this.width).append(", ");
+            result.append("h=").append(this.height);
+            break;
+        default:
+            assert false;
+            throw new AssertionError();
+        }
+
+        return  result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/SerialNumbered.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/SerialNumbered.java
new file mode 100644 (file)
index 0000000..fdecaf5
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * serial-numbered interface
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.Comparator;
+
+/**
+ * 通し番号を持つオブジェクトの抽象化インタフェース。
+ */
+public interface SerialNumbered {
+
+    /** 昇順での比較子。 */
+    Comparator<SerialNumbered> COMPARATOR = new SerialComparator();
+
+    /**
+     * 通し番号を設定する。
+     * @param num 通し番号
+     */
+    void setSerialNumber(int num);
+
+    /**
+     * 通し番号を返す。
+     * @return 通し番号
+     */
+    int getSerialNumber();
+
+    /**
+     * 通し番号による比較子Comparator。
+     * 通し番号の昇順を定義づける。
+     */
+    class SerialComparator
+            implements Comparator<SerialNumbered> {
+
+        /**
+         * コンストラクタ。
+         */
+        public SerialComparator(){
+            super();
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param o1 {@inheritDoc}
+         * @param o2 {@inheritDoc}
+         * @return {@inheritDoc}
+         */
+        @Override
+        public int compare(SerialNumbered o1, SerialNumbered o2){
+            if(o1 == o2) return 0;
+            if(o1 == null) return -1;
+            if(o2 == null) return +1;
+
+            int ser1 = o1.getSerialNumber();
+            int ser2 = o2.getSerialNumber();
+
+            return ser1 - ser2;
+        }
+
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/ShadeInfo.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/ShadeInfo.java
new file mode 100644 (file)
index 0000000..0d9ccf9
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * shading information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+/**
+ * シェーディング情報。
+ */
+public class ShadeInfo {
+
+    private ToonMap toonMap = new ToonMap();
+    private int toonIdx;
+
+    private String textureFileName = null;
+    private String spheremapFileName = null;
+
+    /**
+     * コンストラクタ。
+     */
+    public ShadeInfo(){
+        super();
+        return;
+    }
+
+    /**
+     * トゥーンマップを設定する。
+     * @param map トゥーンマップ
+     */
+    public void setToonMap(ToonMap map){
+        this.toonMap = map;
+        return;
+    }
+
+    /**
+     * トゥーンマップを返す。
+     * @return トゥーンマップ
+     */
+    public ToonMap getToonMap(){
+        return this.toonMap;
+    }
+
+    /**
+     * トゥーンインデックスを設定する。
+     * @param idx トゥーンインデックス
+     */
+    public void setToonIndex(int idx){
+        this.toonIdx = idx;
+        return;
+    }
+
+    /**
+     * トゥーンインデックス値を返す。
+     * @return トゥーンインデックス値
+     */
+    public int getToonIndex(){
+        return this.toonIdx;
+    }
+
+    /**
+     * トゥーンインデックス値が有効か判定する。
+     * 現時点では0から9までの値を有効とする。
+     * @return 有効ならtrue
+     */
+    public boolean isValidToonIndex(){
+        if(0 <= this.toonIdx && this.toonIdx <= 9) return true;
+        return false;
+    }
+
+    /**
+     * トゥーンファイル名を返す。
+     * @return トゥーンファイル名
+     * @throws IllegalStateException トゥーンマップが設定されていない。
+     */
+    public String getToonFileName() throws IllegalStateException{
+        if(this.toonMap == null) throw new IllegalStateException();
+        String result = this.toonMap.getIndexedToon(this.toonIdx);
+        return result;
+    }
+
+    /**
+     * テクスチャファイル名を設定する。
+     * @param fileName テクスチャファイル名
+     */
+    public void setTextureFileName(String fileName){
+        this.textureFileName = fileName;
+        return;
+    }
+
+    /**
+     * テクスチャファイル名を返す。
+     * @return テクスチャファイル名
+     */
+    public String getTextureFileName(){
+        return this.textureFileName;
+    }
+
+    /**
+     * スフィアマップファイル名を設定する。
+     * @param fileName スフィアマップファイル名
+     */
+    public void setSpheremapFileName(String fileName){
+        this.spheremapFileName = fileName;
+        return;
+    }
+
+    /**
+     * スフィアマップファイル名を返す。
+     * @return スフィアマップファイル名
+     */
+    public String getSpheremapFileName(){
+        return this.spheremapFileName;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("toon(")
+              .append(this.toonIdx)
+              .append(")=")
+              .append(getToonFileName())
+              .append(' ');
+        result.append("texture=")
+              .append(this.textureFileName)
+              .append(' ');
+        result.append("sphere=")
+              .append(this.spheremapFileName);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/Surface.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/Surface.java
new file mode 100644 (file)
index 0000000..b710c12
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * triangle surface
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * 3頂点の三角形からなる面情報。
+ */
+public class Surface implements SerialNumbered, Iterable<Vertex> {
+
+    private Vertex vertex1;
+    private Vertex vertex2;
+    private Vertex vertex3;
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     * 3頂点がnullの状態で生成される。
+     */
+    public Surface(){
+        super();
+        return;
+    }
+
+    /**
+     * 3頂点を設定する。
+     * @param vertex1 頂点1
+     * @param vertex2 頂点2
+     * @param vertex3 頂点3
+     * @throws  IllegalArgumentException 重複する頂点が引数に含まれた
+     */
+    public void setTriangle(Vertex vertex1, Vertex vertex2, Vertex vertex3)
+            throws IllegalArgumentException{
+        if(vertex1 != null && (vertex1 == vertex2 || vertex1 == vertex3)){
+            throw new IllegalArgumentException();
+        }
+        if(vertex2 != null && vertex2 == vertex3){
+            throw new IllegalArgumentException();
+        }
+
+        this.vertex1 = vertex1;
+        this.vertex2 = vertex2;
+        this.vertex3 = vertex3;
+
+        return;
+    }
+
+    /**
+     * 3頂点を返す。
+     * @param store 頂点格納用配列。nullもしくは3要素に満たない場合は無視され、
+     * 新規に格納用配列が生成される。
+     * @return 先頭3要素に3頂点が収められた配列。未設定要素にはnullが入る。
+     * 引数が長さ3以上の配列であれば引数と同じ配列が返る。
+     */
+    public Vertex[] getTriangle(Vertex[] store){
+        Vertex[] result;
+        if(store == null || store.length < 3){
+            result = new Vertex[3];
+        }else{
+            result = store;
+        }
+
+        result[0] = this.vertex1;
+        result[1] = this.vertex2;
+        result[2] = this.vertex3;
+
+        return result;
+    }
+
+    /**
+     * 頂点その1を返す。
+     * @return 頂点その1
+     */
+    public Vertex getVertex1(){
+        return this.vertex1;
+    }
+
+    /**
+     * 頂点その2を返す。
+     * @return 頂点その2
+     */
+    public Vertex getVertex2(){
+        return this.vertex2;
+    }
+
+    /**
+     * 頂点その3を返す。
+     * @return 頂点その3
+     */
+    public Vertex getVertex3(){
+        return this.vertex3;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 頂点を返す反復子を生成する。
+     * 反復子がnullを返す可能性もありうる。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Iterator<Vertex> iterator(){
+        List<Vertex> list = new ArrayList<Vertex>(3);
+
+        list.add(this.vertex1);
+        list.add(this.vertex2);
+        list.add(this.vertex3);
+
+        return list.iterator();
+    }
+
+    /**
+     * 3頂点全てが設定されているか判定する。
+     * @return 3頂点とも非nullが設定されていればtrue
+     */
+    public boolean isCompleted(){
+        if(   this.vertex1 != null
+           && this.vertex2 != null
+           && this.vertex3 != null ){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("Surface(")
+              .append(getSerialNumber())
+              .append(")");
+
+        if(isCompleted()){
+            result.append(" VID=[")
+                  .append(this.vertex1.getSerialNumber())
+                  .append(',')
+                  .append(this.vertex2.getSerialNumber())
+                  .append(',')
+                  .append(this.vertex3.getSerialNumber())
+                  .append(']');
+        }
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/ToonMap.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/ToonMap.java
new file mode 100644 (file)
index 0000000..ae9f561
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * toon file mapping
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * インデックス化されたトゥーンファイル構成。
+ * 既存のトゥーンファイル構成と異なるトゥーンファイル名を用いることが可能。
+ * <h1>デフォルトのトゥーンファイル構成。</h1>
+ * <ul>
+ * <li>0x00:toon01.bmp
+ * <li>0x01:toon02.bmp
+ * <li>.....
+ * <li>0x09:toon10.bmp
+ * <li>0xff:toon0.bmp
+ * </ul>
+ */
+public class ToonMap {
+
+    private static final Map<Integer, String> DEF_TOONMAP;
+
+    static{
+        Map<Integer, String> map = new TreeMap<Integer, String>();
+
+        map.put(0x00, "toon01.bmp");
+        map.put(0x01, "toon02.bmp");
+        map.put(0x02, "toon03.bmp");
+        map.put(0x03, "toon04.bmp");
+        map.put(0x04, "toon05.bmp");
+        map.put(0x05, "toon06.bmp");
+        map.put(0x06, "toon07.bmp");
+        map.put(0x07, "toon08.bmp");
+        map.put(0x08, "toon09.bmp");
+        map.put(0x09, "toon10.bmp");
+        map.put(0xff, "toon0.bmp");
+
+        DEF_TOONMAP = Collections.unmodifiableMap(map);
+    }
+
+    private final Map<Integer, String> toonMap =
+            new TreeMap<Integer, String>(DEF_TOONMAP);
+
+    /**
+     * コンストラクタ。
+     */
+    public ToonMap(){
+        super();
+        return;
+    }
+
+    /**
+     * 指定したインデックス値に対応したトゥーンファイル名を返す。
+     * @param idx インデックス値
+     * @return トゥーンファイル名。該当するものがなければnull
+     */
+    public String getIndexedToon(int idx){
+        String result = this.toonMap.get(idx);
+        return result;
+    }
+
+    /**
+     * 指定したインデックス値にトゥーンファイル名を設定する。
+     * @param idx インデックス値
+     * @param toonFileName トゥーンフィル名
+     * @throws NullPointerException トゥーンファイル名がnull
+     */
+    public void setIndexedToon(int idx, String toonFileName)
+            throws NullPointerException{
+        if(toonFileName == null) throw new NullPointerException();
+        this.toonMap.put(idx, toonFileName);
+        return;
+    }
+
+    /**
+     * このトゥーンファイル構成がデフォルトのトゥーンファイル構成と等しいか判定する。
+     * @return 等しければtrue
+     */
+    public boolean isDefaultMap(){
+        if(this.toonMap.equals(DEF_TOONMAP)) return true;
+        return false;
+    }
+
+    /**
+     * 指定インデックスのトゥーンファイル名がデフォルトと等しいか判定する。
+     * @param idx インデックス
+     * @return デフォルトと等しければtrue。
+     */
+    public boolean isDefaultToon(int idx){
+        String thisToon = this.toonMap.get(idx);
+        if(thisToon == null) return false;
+
+        String defToon = DEF_TOONMAP.get(idx);
+        if(thisToon.equals(defToon)) return true;
+
+        return false;
+    }
+
+    /**
+     * このトゥーンファイル構成をデフォルト構成内容でリセットする。
+     */
+    public void resetDefaultMap(){
+        this.toonMap.clear();
+        this.toonMap.putAll(DEF_TOONMAP);
+        return;
+    }
+
+    /**
+     * 指定インデックスのトゥーンファイル名をデフォルトのトゥーンファイル名にリセットする。
+     * @param idx インデックス値
+     */
+    public void resetIndexedToon(int idx){
+        String toonFile = DEF_TOONMAP.get(idx);
+        this.toonMap.put(idx, toonFile);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        boolean dumped = false;
+        for(Map.Entry<Integer, String> entry : this.toonMap.entrySet()){
+            Integer idx = entry.getKey();
+            String toonFile = entry.getValue();
+
+            if(dumped) result.append(", ");
+            result.append('(').append(idx).append(')');
+            result.append(toonFile);
+            dumped = true;
+        }
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/Vertex.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/Vertex.java
new file mode 100644 (file)
index 0000000..7ba6949
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * vertex information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model;
+
+import jp.sourceforge.mikutoga.math.MkPos2D;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkVec3D;
+
+/**
+ * 頂点情報。
+ */
+public class Vertex implements SerialNumbered {
+
+    private static final int MIN_WEIGHT = 0;
+    private static final int MAX_WEIGHT = 100;
+
+    private final MkPos3D position = new MkPos3D();
+    private final MkVec3D normal = new MkVec3D();
+
+    private final MkPos2D uvPosition = new MkPos2D();
+
+    private BoneInfo boneA = null;
+    private BoneInfo boneB = null;
+
+    private int boneWeight = 50;
+
+    private boolean edgeAppearance = true;
+
+    private int serialNo = -1;
+
+    /**
+     * コンストラクタ。
+     */
+    public Vertex(){
+        super();
+        return;
+    }
+
+    /**
+     * 頂点位置座標を返す。
+     * @return 頂点の位置座標
+     */
+    public MkPos3D getPosition(){
+        return this.position;
+    }
+
+    /**
+     * 法線ベクトルを返す。
+     * @return 法線ベクトル
+     */
+    public MkVec3D getNormal(){
+        return this.normal;
+    }
+
+    /**
+     * UVマップ座標を返す。
+     * @return UVマップ情報
+     */
+    public MkPos2D getUVPosition(){
+        return this.uvPosition;
+    }
+
+    /**
+     * 頂点の属するボーンを設定する。
+     * @param boneA ボーンA
+     * @param boneB ボーンB
+     * @throws NullPointerException 引数がnull
+     */
+    public void setBonePair(BoneInfo boneA, BoneInfo boneB)
+            throws NullPointerException{
+        if(boneA == null || boneB == null) throw new NullPointerException();
+        this.boneA = boneA;
+        this.boneB = boneB;
+        return;
+    }
+
+    /**
+     * ボーンAを返す。
+     * @return ボーンA
+     */
+    public BoneInfo getBoneA(){
+        return this.boneA;
+    }
+
+    /**
+     * ボーンBを返す。
+     * @return ボーンB
+     */
+    public BoneInfo getBoneB(){
+        return this.boneB;
+    }
+
+    /**
+     * ボーンAのウェイト値を設定する。
+     * @param weight ウェイト値。0(影響小)-100(影響大)
+     * @throws IllegalArgumentException ウェイト値が範囲外
+     */
+    public void setWeightA(int weight) throws IllegalArgumentException{
+        if(   weight < MIN_WEIGHT
+           || MAX_WEIGHT < weight ){
+            throw new IllegalArgumentException();
+        }
+        this.boneWeight = weight;
+        return;
+    }
+
+    /**
+     * ボーンBのウェイト値を設定する。
+     * @param weight ウェイト値。0(影響小)-100(影響大)
+     * @throws IllegalArgumentException ウェイト値が範囲外
+     */
+    public void setWeightB(int weight) throws IllegalArgumentException{
+        setWeightA(MAX_WEIGHT - weight);
+        return;
+    }
+
+    /**
+     * ボーンAのウェイト値を返す。
+     * @return ウェイト値
+     */
+    public int getWeightA(){
+        return this.boneWeight;
+    }
+
+    /**
+     * ボーンBのウェイト値を返す。
+     * @return ウェイト値
+     */
+    public int getWeightB(){
+        int result = MAX_WEIGHT - this.boneWeight;
+        return result;
+    }
+
+    /**
+     * ボーンAのウェイト率を返す。
+     * @return ウェイト率。0.0(影響小)-1.0(影響大)
+     */
+    public float getWeightRatioA(){
+        return ((float)this.boneWeight) / (float)MAX_WEIGHT;
+    }
+
+    /**
+     * ボーンBのウェイト率を返す。
+     * @return ウェイト率。0.0(影響小)-1.0(影響大)
+     */
+    public float getWeightRatioB(){
+        return ((float)MAX_WEIGHT - (float)this.boneWeight)
+                / (float)MAX_WEIGHT;
+    }
+
+    /**
+     * エッジを表示するか設定する。
+     * マテリアル材質単位の設定より優先度は高い。
+     * @param show 表示するならtrue
+     */
+    public void setEdgeAppearance(boolean show){
+        this.edgeAppearance = show;
+        return;
+    }
+
+    /**
+     * エッジを表示するか判定する。
+     * マテリアル材質単位の設定より優先度は高い。
+     * @return 表示するならtrue
+     */
+    public boolean getEdgeAppearance(){
+        return this.edgeAppearance;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setSerialNumber(int num){
+        this.serialNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSerialNumber(){
+        return this.serialNo;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("Vertex(").append(this.serialNo).append(") ");
+        result.append(this.position).append(' ');
+        result.append(this.normal).append(' ');
+        result.append("UV").append(this.uvPosition).append(' ');
+
+        result.append("[")
+              .append(this.boneA.getBoneName())
+              .append("<>")
+              .append(this.boneB.getBoneName())
+              .append("] ");
+
+        result.append("weight=").append(this.boneWeight).append(' ');
+
+        if(this.edgeAppearance) result.append("showEdge");
+        else                    result.append("hideEdge");
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/BoneBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/BoneBuilder.java
new file mode 100644 (file)
index 0000000..0ae6376
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * building bone information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.IKChain;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.parser.PmdBoneHandler;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+
+/**
+ * ボーン関係の通知をパーサから受け取る。
+ */
+class BoneBuilder implements PmdBoneHandler {
+
+    private final List<BoneInfo> boneList;
+    private Iterator<BoneInfo> boneIt;
+    private BoneInfo currentBone = null;
+
+    private final List<IKChain> ikChainList;
+    private Iterator<IKChain> ikChainIt;
+    private IKChain currentIkChain = null;
+
+    private final List<BoneGroup> boneGroupList;
+    private Iterator<BoneGroup> boneGroupIt;
+    private BoneGroup currentBoneGroup = null;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    BoneBuilder(PmdModel model){
+        super();
+
+        this.boneList      = model.getBoneList();
+        this.ikChainList   = model.getIKChainList();
+        this.boneGroupList = model.getBoneGroupList();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        if(stage == PmdBoneHandler.BONE_LIST){
+            ListUtil.prepareDefConsList(this.boneList, BoneInfo.class, loops);
+            ListUtil.assignIndexedSerial(this.boneList);
+
+            this.boneIt = this.boneList.iterator();
+            if(this.boneIt.hasNext()){
+                this.currentBone = this.boneIt.next();
+            }
+        }else if(stage == PmdBoneHandler.IK_LIST){
+            ListUtil.prepareDefConsList(this.ikChainList,
+                                        IKChain.class,
+                                        loops );
+
+            this.ikChainIt = this.ikChainList.iterator();
+            if(this.ikChainIt.hasNext()){
+                this.currentIkChain = this.ikChainIt.next();
+            }
+        }else if(stage == PmdBoneHandler.IKCHAIN_LIST){
+            //NOTHING
+        }else if(stage == PmdBoneHandler.BONEGROUP_LIST){
+            ListUtil.prepareDefConsList(this.boneGroupList,
+                                        BoneGroup.class,
+                                        loops + 1 );
+            ListUtil.assignIndexedSerial(this.boneGroupList);
+
+            this.boneGroupIt = this.boneGroupList.iterator();
+
+            assert this.boneGroupIt.hasNext();
+            this.boneGroupIt.next();     // デフォルトボーングループを読み飛ばす
+
+            if(this.boneGroupIt.hasNext()){
+                this.currentBoneGroup = this.boneGroupIt.next();
+            }
+        }else if(stage == PmdBoneHandler.GROUPEDBONE_LIST){
+            //NOTHING
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        if(stage == PmdBoneHandler.BONE_LIST){
+            if(this.boneIt.hasNext()){
+                this.currentBone = this.boneIt.next();
+            }
+        }else if(stage == PmdBoneHandler.IK_LIST){
+            if(this.ikChainIt.hasNext()){
+                this.currentIkChain = this.ikChainIt.next();
+            }
+        }else if(stage == PmdBoneHandler.IKCHAIN_LIST){
+            //NOTHING
+        }else if(stage == PmdBoneHandler.BONEGROUP_LIST){
+            if(this.boneGroupIt.hasNext()){
+                this.currentBoneGroup = this.boneGroupIt.next();
+            }
+        }else if(stage == PmdBoneHandler.GROUPEDBONE_LIST){
+            //NOTHING
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        if(stage == PmdBoneHandler.BONE_LIST){
+            //NOTHING
+        }else if(stage == PmdBoneHandler.IK_LIST){
+            //NOTHING
+        }else if(stage == PmdBoneHandler.IKCHAIN_LIST){
+            //NOTHING
+        }else if(stage == PmdBoneHandler.BONEGROUP_LIST){
+            //NOTHING
+        }else if(stage == PmdBoneHandler.GROUPEDBONE_LIST){
+            pickOrphanBone();
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * 所属グループの無いボーンをデフォルトボーングループへ登録する。
+     */
+    private void pickOrphanBone(){
+        List<BoneInfo> orpahnList = new LinkedList<BoneInfo>();
+        orpahnList.addAll(this.boneList);
+        for(BoneGroup group : this.boneGroupList){
+            orpahnList.removeAll(group.getBoneList());
+        }
+
+        BoneGroup defaultGroup = this.boneGroupList.get(0);
+        defaultGroup.getBoneList().addAll(orpahnList);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param boneName {@inheritDoc}
+     * @param boneKind {@inheritDoc}
+     */
+    @Override
+    public void pmdBoneInfo(String boneName, byte boneKind){
+        this.currentBone.getBoneName().setPrimaryText(boneName);
+        BoneType type = BoneType.decode(boneKind);
+        this.currentBone.setBoneType(type);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param parentId {@inheritDoc}
+     * @param tailId {@inheritDoc}
+     * @param ikId {@inheritDoc}
+     */
+    @Override
+    public void pmdBoneLink(int parentId, int tailId, int ikId){
+        BoneInfo prevBone = null;
+        if(0 <= parentId && parentId < PmdLimits.MAX_BONE){
+            prevBone = this.boneList.get(parentId);
+        }
+
+        BoneInfo nextBone = null;
+        if(tailId != 0){
+            nextBone = this.boneList.get(tailId);
+        }
+
+        BoneInfo ikBone = null;
+        if(this.currentBone.getBoneType() == BoneType.LINKEDROT){
+            ikBone = null;
+            int ratio = ikId;
+            this.currentBone.setRotationRatio(ratio);
+        }else if(0 < ikId && ikId < PmdLimits.MAX_BONE){
+            ikBone = this.boneList.get(ikId);
+        }
+
+        this.currentBone.setPrevBone(prevBone);
+        this.currentBone.setNextBone(nextBone);
+        this.currentBone.setIKBone(ikBone);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xPos {@inheritDoc}
+     * @param yPos {@inheritDoc}
+     * @param zPos {@inheritDoc}
+     */
+    @Override
+    public void pmdBonePosition(float xPos, float yPos, float zPos){
+        MkPos3D position = this.currentBone.getPosition();
+        position.setXpos(xPos);
+        position.setYpos(yPos);
+        position.setZpos(zPos);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param boneId {@inheritDoc}
+     * @param targetId {@inheritDoc}
+     * @param depth {@inheritDoc}
+     * @param weight {@inheritDoc}
+     */
+    @Override
+    public void pmdIKInfo(int boneId, int targetId, int depth, float weight){
+        BoneInfo bone = this.boneList.get(boneId);
+        this.currentIkChain.setIkBone(bone);
+
+        BoneInfo target = this.boneList.get(targetId);
+        this.currentIkChain.getChainedBoneList().add(0, target);
+
+        this.currentIkChain.setIKDepth(depth);
+        this.currentIkChain.setIKWeight(weight);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param childId {@inheritDoc}
+     */
+    @Override
+    public void pmdIKChainInfo(int childId){
+        BoneInfo chain = this.boneList.get(childId);
+        this.currentIkChain.getChainedBoneList().add(chain);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param groupName {@inheritDoc}
+     */
+    @Override
+    public void pmdBoneGroupInfo(String groupName){
+        this.currentBoneGroup.getGroupName().setPrimaryText(groupName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param boneId {@inheritDoc}
+     * @param groupId {@inheritDoc}
+     */
+    @Override
+    public void pmdGroupedBoneInfo(int boneId, int groupId){
+        BoneInfo bone = this.boneList.get(boneId);
+        BoneGroup group = this.boneGroupList.get(groupId);
+        group.getBoneList().add(bone);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/JointBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/JointBuilder.java
new file mode 100644 (file)
index 0000000..a03099b
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * building joint information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.Deg3d;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.TripletRange;
+import jp.sourceforge.mikutoga.pmd.model.JointInfo;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.RigidInfo;
+import jp.sourceforge.mikutoga.pmd.parser.PmdJointHandler;
+
+/**
+ * ジョイント関係の通知をパーサから受け取る。
+ */
+class JointBuilder implements PmdJointHandler {
+
+    private final List<RigidInfo> rigidList;
+
+    private final List<JointInfo> jointList;
+    private Iterator<JointInfo> jointIt;
+    private JointInfo currentJoint = null;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    JointBuilder(PmdModel model){
+        super();
+        this.rigidList = model.getRigidList();
+        this.jointList = model.getJointList();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        assert stage == PmdJointHandler.JOINT_LIST;
+
+        ListUtil.prepareDefConsList(this.jointList, JointInfo.class, loops);
+
+        this.jointIt = this.jointList.iterator();
+        if(this.jointIt.hasNext()){
+            this.currentJoint = this.jointIt.next();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        assert stage == PmdJointHandler.JOINT_LIST;
+
+        if(this.jointIt.hasNext()){
+            this.currentJoint = this.jointIt.next();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        assert stage == PmdJointHandler.JOINT_LIST;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param jointName {@inheritDoc}
+     */
+    @Override
+    public void pmdJointName(String jointName){
+        this.currentJoint.getJointName().setPrimaryText(jointName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rigidIdA {@inheritDoc}
+     * @param rigidIdB {@inheritDoc}
+     */
+    @Override
+    public void pmdJointLink(int rigidIdA, int rigidIdB){
+        RigidInfo rigidA = this.rigidList.get(rigidIdA);
+        RigidInfo rigidB = this.rigidList.get(rigidIdB);
+        this.currentJoint.setRigidPair(rigidA, rigidB);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param posX {@inheritDoc}
+     * @param posY {@inheritDoc}
+     * @param posZ {@inheritDoc}
+     */
+    @Override
+    public void pmdJointPosition(float posX, float posY, float posZ){
+        MkPos3D position = this.currentJoint.getPosition();
+        position.setXpos(posX);
+        position.setYpos(posY);
+        position.setZpos(posZ);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rotX {@inheritDoc}
+     * @param rotY {@inheritDoc}
+     * @param rotZ {@inheritDoc}
+     */
+    @Override
+    public void pmdJointRotation(float rotX, float rotY, float rotZ){
+        Rad3d rotation = this.currentJoint.getRotation();
+        rotation.setXRad(rotX);
+        rotation.setYRad(rotY);
+        rotation.setZRad(rotZ);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param posXlim1 {@inheritDoc}
+     * @param posXlim2 {@inheritDoc}
+     * @param posYlim1 {@inheritDoc}
+     * @param posYlim2 {@inheritDoc}
+     * @param posZlim1 {@inheritDoc}
+     * @param posZlim2 {@inheritDoc}
+     */
+    @Override
+    public void pmdPositionLimit(float posXlim1, float posXlim2,
+                                 float posYlim1, float posYlim2,
+                                 float posZlim1, float posZlim2){
+        TripletRange range = this.currentJoint.getPositionRange();
+        range.setXRange(posXlim1, posXlim2);
+        range.setYRange(posYlim1, posYlim2);
+        range.setZRange(posZlim1, posZlim2);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rotXlim1 {@inheritDoc}
+     * @param rotXlim2 {@inheritDoc}
+     * @param rotYlim1 {@inheritDoc}
+     * @param rotYlim2 {@inheritDoc}
+     * @param rotZlim1 {@inheritDoc}
+     * @param rotZlim2 {@inheritDoc}
+     */
+    @Override
+    public void pmdRotationLimit(float rotXlim1, float rotXlim2,
+                                 float rotYlim1, float rotYlim2,
+                                 float rotZlim1, float rotZlim2){
+        TripletRange range = this.currentJoint.getRotationRange();
+        range.setXRange(rotXlim1, rotXlim2);
+        range.setYRange(rotYlim1, rotYlim2);
+        range.setZRange(rotZlim1, rotZlim2);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param elasticPosX {@inheritDoc}
+     * @param elasticPosY {@inheritDoc}
+     * @param elasticPosZ {@inheritDoc}
+     */
+    @Override
+    public void pmdElasticPosition(float elasticPosX,
+                                   float elasticPosY,
+                                   float elasticPosZ){
+        MkPos3D position = this.currentJoint.getElasticPosition();
+        position.setXpos(elasticPosX);
+        position.setYpos(elasticPosY);
+        position.setZpos(elasticPosZ);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param elasticRotX {@inheritDoc}
+     * @param elasticRotY {@inheritDoc}
+     * @param elasticRotZ {@inheritDoc}
+     */
+    @Override
+    public void pmdElasticRotation(float elasticRotX,
+                                   float elasticRotY,
+                                   float elasticRotZ){
+        Deg3d rotation = this.currentJoint.getElasticRotation();
+        rotation.setXDeg(elasticRotX);
+        rotation.setYDeg(elasticRotY);
+        rotation.setZDeg(elasticRotZ);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/MaterialBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/MaterialBuilder.java
new file mode 100644 (file)
index 0000000..8ffdaec
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * building material information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.awt.Color;
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.model.Material;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
+import jp.sourceforge.mikutoga.pmd.model.Surface;
+import jp.sourceforge.mikutoga.pmd.model.ToonMap;
+import jp.sourceforge.mikutoga.pmd.parser.PmdMaterialHandler;
+
+/**
+ * マテリアル素材関連の通知をパーサから受け取る。
+ */
+class MaterialBuilder implements PmdMaterialHandler {
+
+    private final List<Material> materialList;
+    private Iterator<Material> materialIt;
+    private Material currentMaterial = null;
+
+    private final List<Surface> surfacelList;
+    private Iterator<Surface> surfaceIt;
+
+    private final ToonMap toonMap;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    MaterialBuilder(PmdModel model){
+        super();
+
+        this.materialList = model.getMaterialList();
+        this.surfacelList = model.getSurfaceList();
+        this.toonMap = model.getToonMap();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        assert stage == PmdMaterialHandler.MATERIAL_LIST;
+
+        ListUtil.prepareDefConsList(this.materialList, Material.class, loops);
+
+        this.materialIt = this.materialList.iterator();
+        if(this.materialIt.hasNext()){
+            this.currentMaterial = this.materialIt.next();
+        }
+
+        this.surfaceIt = this.surfacelList.iterator();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        assert stage == PmdMaterialHandler.MATERIAL_LIST;
+
+        if(this.materialIt.hasNext()){
+            this.currentMaterial = this.materialIt.next();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        assert stage == PmdMaterialHandler.MATERIAL_LIST;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param red {@inheritDoc}
+     * @param green {@inheritDoc}
+     * @param blue {@inheritDoc}
+     * @param alpha {@inheritDoc}
+     */
+    @Override
+    public void pmdMaterialDiffuse(float red,
+                                   float green,
+                                   float blue,
+                                   float alpha ){
+        Color diffuse = new Color(red, green, blue, alpha);
+        this.currentMaterial.setDiffuseColor(diffuse);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param red {@inheritDoc}
+     * @param green {@inheritDoc}
+     * @param blue {@inheritDoc}
+     */
+    @Override
+    public void pmdMaterialAmbient(float red,
+                                   float green,
+                                   float blue ){
+        Color ambient = new Color(red, green, blue);
+        this.currentMaterial.setAmbientColor(ambient);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param red {@inheritDoc}
+     * @param green {@inheritDoc}
+     * @param blue {@inheritDoc}
+     * @param shininess {@inheritDoc}
+     */
+    @Override
+    public void pmdMaterialSpecular(float red,
+                                    float green,
+                                    float blue,
+                                    float shininess ){
+        Color specular = new Color(red, green, blue);
+        this.currentMaterial.setSpecularColor(specular);
+        this.currentMaterial.setShininess(shininess);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param hasEdge {@inheritDoc}
+     * @param vertexNum {@inheritDoc}
+     */
+    @Override
+    public void pmdMaterialInfo(boolean hasEdge, int vertexNum){
+        this.currentMaterial.setEdgeAppearance(hasEdge);
+
+        List<Surface> list = this.currentMaterial.getSurfaceList();
+
+        int surfaceNum = vertexNum / 3;
+        for(int ct = 1; ct <= surfaceNum; ct++){
+            Surface surface = this.surfaceIt.next();
+            list.add(surface);
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param toonIdx {@inheritDoc}
+     * @param textureFile {@inheritDoc}
+     * @param sphereFile {@inheritDoc}
+     */
+    @Override
+    public void pmdMaterialShading(int toonIdx,
+                                   String textureFile,
+                                   String sphereFile ){
+        ShadeInfo info = this.currentMaterial.getShadeInfo();
+
+        ToonMap map = this.toonMap;
+
+        info.setToonMap(map);
+        info.setToonIndex(toonIdx);
+        info.setTextureFileName(textureFile);
+        info.setSpheremapFileName(sphereFile);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/MorphBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/MorphBuilder.java
new file mode 100644 (file)
index 0000000..4e4df22
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * building morph information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.Vertex;
+import jp.sourceforge.mikutoga.pmd.parser.PmdMorphHandler;
+
+/**
+ * モーフ関係の通知をパーサから受け取る。
+ */
+class MorphBuilder implements PmdMorphHandler {
+
+    private final Map<MorphType, List<MorphPart>> morphMap;
+
+    private List<MorphPart> morphPartList;
+    private Iterator<MorphPart> morphPartIt;
+    private MorphPart currentMorphPart;
+    private final List<Vertex> vertexList;
+
+    private final List<Vertex> morphVertexList = new ArrayList<Vertex>();
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    MorphBuilder(PmdModel model){
+        super();
+        this.vertexList = model.getVertexList();
+        this.morphMap = model.getMorphMap();
+        return;
+    }
+
+    /**
+     * PMDファイル中の出現順で各モーフを格納するためのリストを設定する。
+     * 主な用途はモーフ英名との突き合わせ作業。
+     * @param list モーフ格納リスト
+     */
+    void setMorphPartList(List<MorphPart> list){
+        this.morphPartList = list;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        if(stage == PmdMorphHandler.MORPH_LIST){
+            ListUtil.prepareDefConsList(this.morphPartList,
+                                        MorphPart.class,
+                                        loops );
+            ListUtil.assignIndexedSerial(this.morphPartList);
+
+            this.morphPartIt = this.morphPartList.iterator();
+            if(this.morphPartIt.hasNext()){
+                this.currentMorphPart = this.morphPartIt.next();
+            }
+        }else if(stage == PmdMorphHandler.MORPHVERTEX_LIST){
+            // NOTHING
+        }else if(stage == PmdMorphHandler.MORPHORDER_LIST){
+            // NOTHING
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        if(stage == PmdMorphHandler.MORPH_LIST){
+            if(this.morphPartIt.hasNext()){
+                this.currentMorphPart = this.morphPartIt.next();
+            }
+        }else if(stage == PmdMorphHandler.MORPHVERTEX_LIST){
+            // NOTHING
+        }else if(stage == PmdMorphHandler.MORPHORDER_LIST){
+            // NOTHING
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        if(stage == PmdMorphHandler.MORPH_LIST){
+            // NOTHING
+        }else if(stage == PmdMorphHandler.MORPHVERTEX_LIST){
+            // NOTHING
+        }else if(stage == PmdMorphHandler.MORPHORDER_LIST){
+            // NOTHING
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param morphName {@inheritDoc}
+     * @param morphType {@inheritDoc}
+     */
+    @Override
+    public void pmdMorphInfo(String morphName, byte morphType){
+        this.currentMorphPart.getMorphName().setPrimaryText(morphName);
+        MorphType type = MorphType.decode(morphType);
+        this.currentMorphPart.setMorphType(type);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param serialId {@inheritDoc}
+     * @param xPos {@inheritDoc}
+     * @param yPos {@inheritDoc}
+     * @param zPos {@inheritDoc}
+     */
+    @Override
+    public void pmdMorphVertexInfo(int serialId,
+                                   float xPos, float yPos, float zPos){
+        MorphVertex morphVertex;
+        morphVertex = new MorphVertex();
+        MkPos3D position = morphVertex.getOffset();
+        position.setXpos(xPos);
+        position.setYpos(yPos);
+        position.setZpos(zPos);
+
+        Vertex vertex;
+        if(this.currentMorphPart.getMorphType().isBase()){
+            vertex = this.vertexList.get(serialId);
+            this.morphVertexList.add(vertex);
+        }else{
+            vertex = this.morphVertexList.get(serialId);
+        }
+        morphVertex.setBaseVertex(vertex);
+
+        this.currentMorphPart.getMorphVertexList().add(morphVertex);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param morphId {@inheritDoc}
+     */
+    @Override
+    public void pmdMorphOrderInfo(int morphId){
+        MorphPart part = this.morphPartList.get(morphId);
+        MorphType type = part.getMorphType();
+
+        List<MorphPart> partList = this.morphMap.get(type);
+        if(partList == null){
+            partList = new LinkedList<MorphPart>();
+            this.morphMap.put(type, partList);
+        }
+        partList.add(part);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporter.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporter.java
new file mode 100644 (file)
index 0000000..0cc9280
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * model exporter for pmd-file(up to date)
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.io.OutputStream;
+
+/**
+ * 最新仕様のPMDファイルエクスポーター。
+ * 将来のリリースにおいて、
+ * 常に最新のPMDモデルファイルフォーマットに対応したエクスポーターの
+ * 別名であることが保証される。つもり。
+ */
+public class PmdExporter extends PmdExporterExt3{
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     * @throws NullPointerException 引数がnull
+     */
+    public PmdExporter(OutputStream stream) throws NullPointerException{
+        super(stream);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterBase.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterBase.java
new file mode 100644 (file)
index 0000000..6122d05
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+ * model exporter for pmd-file
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import jp.sourceforge.mikutoga.pmd.IllegalPmdDataException;
+import java.awt.Color;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import jp.sourceforge.mikutoga.binio.BinaryExporter;
+import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
+import jp.sourceforge.mikutoga.math.MkPos2D;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkVec3D;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.IKChain;
+import jp.sourceforge.mikutoga.pmd.model.Material;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.SerialNumbered;
+import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
+import jp.sourceforge.mikutoga.pmd.model.Surface;
+import jp.sourceforge.mikutoga.pmd.model.Vertex;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+
+/**
+ * PMDファイルのエクスポーター(拡張無し基本フォーマット)。
+ * <p>
+ * 英名対応以降のPMDファイルフォーマットを
+ * 使いたくない場合はこのエクスポーターを用いて出力せよ。
+ */
+public class PmdExporterBase extends BinaryExporter{
+
+    /** 前(親)ボーンが無い場合の便宜的なボーンID。 */
+    public static final int NOPREVBONE_ID = 0xffff;
+    /** 次(子)ボーンが無い場合の便宜的なボーンID。 */
+    public static final int NONEXTBONE_ID = 0x0000;
+    /** 影響元IKボーンが無い場合の便宜的なボーンID。 */
+    public static final int NOIKBONE_ID = 0x0000;
+
+    private static final byte[] MAGIC_BYTES = {
+        (byte)0x50, (byte)0x6d, (byte)0x64,               // "Pmd"
+        (byte)0x00, (byte)0x00, (byte)0x80, (byte)0x3f,   // 1.0f
+    };
+
+    private static final byte[] NULLFILLER =
+        { (byte)0x00 };
+    private static final byte[] FDFILLER =
+        { (byte)0x00, (byte)0xfd };
+    private static final byte[] LFFILLER =
+        { (byte)0x0a, (byte)0x00, (byte)0xfd };
+
+    /** 改行文字列 CR。 */
+    private static final String CR = "\r";       // 0x0d
+    /** 改行文字列 LF。 */
+    private static final String LF = "\n";       // 0x0a
+    /** 改行文字列 CRLF。 */
+    private static final String CRLF = CR + LF;  // 0x0d, 0x0a
+
+    static{
+        assert NOPREVBONE_ID > PmdLimits.MAX_BONE - 1;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     * @throws NullPointerException 引数がnull
+     */
+    public PmdExporterBase(OutputStream stream)
+            throws NullPointerException{
+        super(stream);
+        return;
+    }
+
+    /**
+     * 改行文字の正規化を行う。
+     * CR(0x0d)およびCRLF(0x0d0a)がLF(0x0a)へと正規化される。
+     * @param text 文字列
+     * @return 正規化の行われた文字列。
+     */
+    protected static String normalizeBreak(String text){
+        String result = text;
+
+        result = result.replace(CRLF, LF);
+        result = result.replace(CR, LF);
+
+        return result;
+    }
+
+    /**
+     * 文字列を指定されたバイト長で出力する。
+     * 文字列の改行記号はLF(0x0a)に正規化される。
+     * エンコード結果がバイト長に満たない場合は
+     * 1つの0x00及びそれに続く複数の0xfdがパディングされる。
+     * @param text 文字列
+     * @param maxByteLength バイト長指定
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException エンコード結果が
+     * 指定バイト長をはみ出した。
+     */
+    protected void dumpText(String text, int maxByteLength)
+            throws IOException, IllegalTextExportException{
+        dumpFixedW31j(text, maxByteLength, FDFILLER);
+        return;
+    }
+
+    /**
+     * モデルデータをPMDファイル形式で出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdDataException モデルデータに不備が発見された
+     */
+    public void dumpPmdModel(PmdModel model)
+            throws IOException, IllegalPmdDataException{
+        try{
+            dumpBasic(model);
+            dumpVertexList(model);
+            dumpSurfaceList(model);
+            dumpMaterialList(model);
+            dumpBoneList(model);
+            dumpIKChainList(model);
+            dumpMorphList(model);
+            dumpMorphGroup(model);
+            dumpBoneGroupList(model);
+        }catch(IllegalTextExportException e){
+            throw new IllegalPmdDataException(e);
+        }
+
+        return;
+    }
+
+    /**
+     * モデル基本情報を出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException モデル名もしくは説明が長すぎる
+     */
+    private void dumpBasic(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        for(int idx=0; idx < MAGIC_BYTES.length; idx++){
+            dumpByte(MAGIC_BYTES[idx]);
+        }
+
+        String modelName   = model.getModelName()  .getPrimaryText();
+        String description = model.getDescription().getPrimaryText();
+
+        dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
+        dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 頂点リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpVertexList(PmdModel model)
+            throws IOException{
+        List<Vertex> vList = model.getVertexList();
+
+        int vertexNum = vList.size();
+        dumpInt(vertexNum);
+
+        for(Vertex vertex : vList){
+            dumpVertex(vertex);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 個別の頂点データを出力する。
+     * @param vertex 頂点
+     * @throws IOException 出力エラー
+     */
+    private void dumpVertex(Vertex vertex)
+            throws IOException{
+        MkPos3D position = vertex.getPosition();
+        dumpPos3D(position);
+
+        MkVec3D normal = vertex.getNormal();
+        dumpVec3D(normal);
+
+        MkPos2D uv = vertex.getUVPosition();
+        dumpPos2d(uv);
+
+        BoneInfo boneA = vertex.getBoneA();
+        BoneInfo boneB = vertex.getBoneB();
+        dumpSerialIdAsShort(boneA);
+        dumpSerialIdAsShort(boneB);
+
+        int weight = vertex.getWeightA();
+        dumpByte((byte)weight);
+
+        byte edgeFlag;
+        boolean hasEdge = vertex.getEdgeAppearance();
+        if(hasEdge) edgeFlag = 0x00;
+        else        edgeFlag = 0x01;
+        dumpByte(edgeFlag);
+
+        return;
+    }
+
+    /**
+     * 面リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpSurfaceList(PmdModel model)
+            throws IOException{
+        int surfaceNum = 0;
+        List<Material> materialList = model.getMaterialList();
+        for(Material material : materialList){
+            surfaceNum += material.getSurfaceList().size();
+        }
+        dumpInt(surfaceNum * 3);
+
+        Vertex[] triangle = new Vertex[3];
+        for(Material material : materialList){
+            for(Surface surface : material){
+                surface.getTriangle(triangle);
+                dumpShort(triangle[0].getSerialNumber());
+                dumpShort(triangle[1].getSerialNumber());
+                dumpShort(triangle[2].getSerialNumber());
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * マテリアル素材リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException シェーディングファイル情報が長すぎる
+     */
+    private void dumpMaterialList(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        List<Material> materialList = model.getMaterialList();
+
+        int materialNum = materialList.size();
+        dumpInt(materialNum);
+
+        float[] rgba = new float[4];
+
+        for(Material material : materialList){
+            Color diffuse = material.getDiffuseColor();
+            diffuse.getRGBComponents(rgba);
+            dumpFloat(rgba[0]);
+            dumpFloat(rgba[1]);
+            dumpFloat(rgba[2]);
+            dumpFloat(rgba[3]);
+
+            float shininess = material.getShininess();
+            dumpFloat(shininess);
+
+            Color specular = material.getSpecularColor();
+            specular.getRGBComponents(rgba);
+            dumpFloat(rgba[0]);
+            dumpFloat(rgba[1]);
+            dumpFloat(rgba[2]);
+
+            Color ambient = material.getAmbientColor();
+            ambient.getRGBComponents(rgba);
+            dumpFloat(rgba[0]);
+            dumpFloat(rgba[1]);
+            dumpFloat(rgba[2]);
+
+            ShadeInfo shade = material.getShadeInfo();
+            int toonIdx = shade.getToonIndex();
+            dumpByte(toonIdx);
+
+            byte edgeFlag;
+            boolean showEdge = material.getEdgeAppearance();
+            if(showEdge) edgeFlag = 0x01;
+            else         edgeFlag = 0x00;
+            dumpByte(edgeFlag);
+
+            int surfaceNum = material.getSurfaceList().size();
+            dumpInt(surfaceNum * 3);
+
+            dumpShadeFileInfo(shade);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * シェーディングファイル情報を出力する。
+     * @param shade シェーディング情報
+     * @throws IOException 出力エラー
+     * @throws IllegalTextExportException ファイル名が長すぎる
+     */
+    private void dumpShadeFileInfo(ShadeInfo shade)
+            throws IOException, IllegalTextExportException{
+        String textureFile   = shade.getTextureFileName();
+        String spheremapFile = shade.getSpheremapFileName();
+
+        StringBuilder text = new StringBuilder();
+        if(textureFile != null) text.append(textureFile);
+        if(spheremapFile != null && spheremapFile.length() > 0){
+            text.append('*')
+                  .append(spheremapFile);
+        }
+
+        byte[] filler;
+        if(text.length() <= 0) filler = NULLFILLER;
+        else                   filler = FDFILLER;
+
+        dumpFixedW31j(text.toString(),
+                      PmdLimits.MAXBYTES_TEXTUREFILENAME,
+                      filler );
+
+        return;
+    }
+
+    /**
+     * ボーンリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException ボーン名が長すぎる
+     */
+    private void dumpBoneList(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        List<BoneInfo> boneList = model.getBoneList();
+
+        int boneNum = boneList.size();
+        dumpShort(boneNum);
+
+        for(BoneInfo bone : boneList){
+            dumpBone(bone);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 個別のボーン情報を出力する。
+     * @param bone ボーン情報
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException ボーン名が長すぎる
+     */
+    private void dumpBone(BoneInfo bone)
+            throws IOException, IllegalTextExportException{
+        String boneName = bone.getBoneName().getPrimaryText();
+        dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
+
+        BoneInfo prev = bone.getPrevBone();
+        if(prev != null) dumpSerialIdAsShort(prev);
+        else             dumpShort(NOPREVBONE_ID);
+
+        BoneInfo next = bone.getNextBone();
+        if(next != null) dumpSerialIdAsShort(next);
+        else             dumpShort(NONEXTBONE_ID);
+
+        BoneType type = bone.getBoneType();
+        dumpByte(type.encode());
+
+        if(type == BoneType.LINKEDROT){
+            int ratio = bone.getRotationRatio();
+            dumpShort(ratio);
+        }else{
+            BoneInfo ik = bone.getIKBone();
+            if(ik != null) dumpSerialIdAsShort(ik);
+            else           dumpShort(NOIKBONE_ID);
+        }
+
+        MkPos3D position = bone.getPosition();
+        dumpPos3D(position);
+
+        return;
+    }
+
+    /**
+     * IKチェーンリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpIKChainList(PmdModel model)
+            throws IOException{
+        List<IKChain> ikChainList = model.getIKChainList();
+
+        int ikNum = ikChainList.size();
+        dumpShort(ikNum);
+
+        for(IKChain chain : ikChainList){
+            dumpIKChain(chain);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * IKチェーンを出力する。
+     * @param chain IKチェーン
+     * @throws IOException 出力エラー
+     */
+    // TODO ボーンリストから自動抽出できる情報ではないのか?
+    private void dumpIKChain(IKChain chain)
+            throws IOException{
+        BoneInfo ikBone = chain.getIkBone();
+        dumpSerialIdAsShort(ikBone);
+
+        List<BoneInfo> boneList = chain.getChainedBoneList();
+
+        BoneInfo bone1st = boneList.get(0);
+        dumpSerialIdAsShort(bone1st);
+
+        int boneNum = boneList.size();
+        dumpByte(boneNum - 1);
+
+        int depth = chain.getIKDepth();
+        float weight = chain.getIKWeight();
+
+        dumpShort(depth);
+        dumpFloat(weight);
+
+        for(int idx = 1; idx < boneNum; idx++){ // リストの2番目以降全て
+            BoneInfo bone = boneList.get(idx);
+            dumpSerialIdAsShort(bone);
+        }
+
+        return;
+    }
+
+    /**
+     * モーフリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException モーフ名が長すぎる
+     */
+    private void dumpMorphList(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+        Set<MorphType> typeSet = morphMap.keySet();
+        List<MorphVertex> mergedMorphVertexList = model.mergeMorphVertex();
+
+        int totalMorphPart = 0;
+        for(MorphType type : typeSet){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            totalMorphPart += partList.size();
+        }
+
+        if(totalMorphPart <= 0){
+            dumpShort(0);
+            return;
+        }else{
+            totalMorphPart++;  // baseの分
+            dumpShort(totalMorphPart);
+        }
+
+        dumpText("base", PmdLimits.MAXBYTES_MORPHNAME);
+        int totalVertex = mergedMorphVertexList.size();
+        dumpInt(totalVertex);
+        dumpByte(MorphType.BASE.encode());
+        for(MorphVertex morphVertex : mergedMorphVertexList){
+            Vertex baseVertex = morphVertex.getBaseVertex();
+            dumpInt(baseVertex.getSerialNumber());
+            dumpPos3D(baseVertex.getPosition());
+        }
+
+        for(MorphType type : typeSet){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                dumpText(part.getMorphName().getPrimaryText(),
+                         PmdLimits.MAXBYTES_MORPHNAME );
+                List<MorphVertex> morphVertexList = part.getMorphVertexList();
+                dumpInt(morphVertexList.size());
+                dumpByte(part.getMorphType().encode());
+
+                for(MorphVertex morphVertex : morphVertexList){
+                    dumpInt(morphVertex.getSerialNumber());
+                    dumpPos3D(morphVertex.getOffset());
+                }
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * モーフグループを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpMorphGroup(PmdModel model)
+            throws IOException{
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+        Set<MorphType> typeSet = morphMap.keySet();
+
+        int totalMorph = 0;
+        for(MorphType type : typeSet){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            totalMorph += partList.size();
+        }
+        dumpByte(totalMorph);
+
+        List<MorphType> typeList = new LinkedList<MorphType>();
+        for(MorphType type : typeSet){
+            assert ! type.isBase();
+            typeList.add(type);
+        }
+        Collections.reverse(typeList);  // 一応本家と互換性を
+
+        for(MorphType type : typeList){
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                dumpSerialIdAsShort(part);
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * ボーングループリストを出力する。
+     * デフォルトボーングループ内訳は出力されない。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalTextExportException ボーングループ名が長すぎる
+     */
+    private void dumpBoneGroupList(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        List<BoneGroup> groupList = model.getBoneGroupList();
+        int groupNum = groupList.size();
+        dumpByte(groupNum - 1);
+
+        int dispBoneNum = 0;
+        for(BoneGroup group : groupList){
+            if(group.isDefaultBoneGroup()) continue;
+            dumpFixedW31j(group.getGroupName().getPrimaryText(),
+                          PmdLimits.MAXBYTES_BONEGROUPNAME, LFFILLER );
+            dispBoneNum += group.getBoneList().size();
+        }
+        dumpInt(dispBoneNum);
+
+        for(BoneGroup group : groupList){
+            if(group.isDefaultBoneGroup()) continue;
+            for(BoneInfo bone : group){
+                dumpSerialIdAsShort(bone);
+                int groupId = group.getSerialNumber();
+                dumpByte(groupId);
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 各種通し番号をshort値で出力する。
+     * short値に収まらない上位ビットは捨てられる。
+     * @param obj 番号づけられたオブジェクト
+     * @throws IOException 出力エラー
+     */
+    protected void dumpSerialIdAsShort(SerialNumbered obj)
+            throws IOException{
+        int serialId = obj.getSerialNumber();
+        dumpShort(serialId);
+        return;
+    }
+
+    /**
+     * 2次元位置情報を出力する。
+     * @param position 2次元位置情報
+     * @throws IOException 出力エラー
+     */
+    protected void dumpPos2d(MkPos2D position) throws IOException{
+        float xPos = (float) position.getXpos();
+        float yPos = (float) position.getYpos();
+
+        dumpFloat(xPos);
+        dumpFloat(yPos);
+
+        return;
+    }
+
+    /**
+     * 3次元位置情報を出力する。
+     * @param position 3次元位置情報
+     * @throws IOException 出力エラー
+     */
+    protected void dumpPos3D(MkPos3D position) throws IOException{
+        float xPos = (float) position.getXpos();
+        float yPos = (float) position.getYpos();
+        float zPos = (float) position.getZpos();
+
+        dumpFloat(xPos);
+        dumpFloat(yPos);
+        dumpFloat(zPos);
+
+        return;
+    }
+
+    /**
+     * 3次元ベクトル情報を出力する。
+     * @param vector 3次元ベクトル
+     * @throws IOException 出力エラー
+     */
+    protected void dumpVec3D(MkVec3D vector) throws IOException{
+        float xVal = (float) vector.getXVal();
+        float yVal = (float) vector.getYVal();
+        float zVal = (float) vector.getZVal();
+
+        dumpFloat(xVal);
+        dumpFloat(yVal);
+        dumpFloat(zVal);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt1.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt1.java
new file mode 100644 (file)
index 0000000..c55cedd
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * model exporter for pmd-file(Ext1)
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import jp.sourceforge.mikutoga.pmd.IllegalPmdDataException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+
+/**
+ * PMDファイルのエクスポーター(拡張1:英名対応)。
+ * <p>
+ * 任意のトゥーンファイル名対応以降のPMDファイルフォーマットを
+ * 使いたくない場合はこのエクスポーターを用いて出力せよ。
+ */
+public class PmdExporterExt1 extends PmdExporterBase{
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     * @throws NullPointerException 引数がnull
+     */
+    public PmdExporterExt1(OutputStream stream)
+            throws NullPointerException{
+        super(stream);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param model {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     * @throws IllegalPmdDataException {@inheritDoc}
+     */
+    @Override
+    public void dumpPmdModel(PmdModel model)
+            throws IOException, IllegalPmdDataException{
+        super.dumpPmdModel(model);
+
+        dumpGlobalInfo(model);
+
+        return;
+    }
+
+    /**
+     * 英語名情報を出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 文字列が長すぎる。
+     */
+    private void dumpGlobalInfo(PmdModel model)
+            throws IOException, IllegalPmdDataException{
+        boolean hasGlobal = model.hasGlobalText();
+        byte globalFlag;
+        if(hasGlobal) globalFlag = 0x01;
+        else          globalFlag = 0x00;
+        dumpByte(globalFlag);
+
+        if(hasGlobal){
+            try{
+                dumpBasicGlobal(model);
+                dumpBoneGlobal(model);
+                dumpMorphGlobal(model);
+                dumpBoneGroupGlobal(model);
+            }catch(IllegalTextExportException e){
+                throw new IllegalPmdDataException(e);
+            }
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * モデル基本情報を英語で出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 文字列が長すぎる。
+     */
+    private void dumpBasicGlobal(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        String modelName = model.getModelName().getGlobalText();
+        if(modelName == null) modelName = "";
+        dumpText(modelName, PmdLimits.MAXBYTES_MODELNAME);
+
+        String description = model.getDescription().getGlobalText();
+        if(description == null) description = "";
+        dumpText(description, PmdLimits.MAXBYTES_MODELDESC);
+
+        flush();
+    }
+
+    /**
+     * ボーン英語名情報を出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 文字列が長すぎる。
+     */
+    private void dumpBoneGlobal(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        for(BoneInfo bone : model.getBoneList()){
+            String boneName = bone.getBoneName().getGlobalText();
+            if(boneName == null) boneName = "";
+            dumpText(boneName, PmdLimits.MAXBYTES_BONENAME);
+        }
+
+        flush();
+    }
+
+    /**
+     * モーフ英語名情報を出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 文字列が長すぎる。
+     */
+    private void dumpMorphGlobal(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+
+        for(MorphType type : MorphType.values()){
+            if(type.isBase()) continue;
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                String morphName = part.getMorphName().getGlobalText();
+                if(morphName == null) morphName = "";
+                dumpText(morphName, PmdLimits.MAXBYTES_MORPHNAME);
+            }
+        }
+
+        flush();
+    }
+
+    /**
+     * ボーングループ英語名情報を出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 文字列が長すぎる
+     */
+    private void dumpBoneGroupGlobal(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        for(BoneGroup group : model.getBoneGroupList()){
+            if(group.isDefaultBoneGroup()) continue;
+            String groupName = group.getGroupName().getGlobalText();
+            if(groupName == null) groupName = "";
+            dumpText(groupName, PmdLimits.MAXBYTES_BONEGROUPNAME);
+        }
+
+        flush();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt2.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt2.java
new file mode 100644 (file)
index 0000000..4fcc46e
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * model exporter for pmd-file(Ext2)
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import jp.sourceforge.mikutoga.pmd.IllegalPmdDataException;
+import java.io.IOException;
+import java.io.OutputStream;
+import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.ToonMap;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+
+/**
+ * PMDファイルのエクスポーター(拡張2:任意のトゥーンファイル名対応)。
+ * <p>
+ * 物理演算対応以降のPMDファイルフォーマットを
+ * 使いたくない場合はこのエクスポーターを用いて出力せよ。
+ */
+public class PmdExporterExt2 extends PmdExporterExt1{
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     * @throws NullPointerException 引数がnull
+     */
+    public PmdExporterExt2(OutputStream stream)
+            throws NullPointerException{
+        super(stream);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param model {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     * @throws IllegalPmdDataException {@inheritDoc}
+     */
+    @Override
+    public void dumpPmdModel(PmdModel model)
+            throws IOException, IllegalPmdDataException{
+        super.dumpPmdModel(model);
+
+        try{
+            dumpToonMap(model);
+        }catch(IllegalTextExportException e){
+            throw new IllegalPmdDataException(e);
+        }
+
+        return;
+    }
+
+    /**
+     * 独自トゥーンファイルテーブルを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException トゥーンファイル名が長すぎる
+     */
+    private void dumpToonMap(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        ToonMap map = model.getToonMap();
+
+        for(int idx = 0; idx < PmdLimits.TOON_FIXEDNUM; idx++){
+            String toonName = map.getIndexedToon(idx);
+            if(toonName == null) toonName = "";
+            dumpText(toonName, PmdLimits.MAXBYTES_TOONFILENAME);
+        }
+
+        flush();
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt3.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdExporterExt3.java
new file mode 100644 (file)
index 0000000..b496a54
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * model exporter for pmd-file(Ext3)
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import jp.sourceforge.mikutoga.pmd.IllegalPmdDataException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
+import jp.sourceforge.mikutoga.pmd.Deg3d;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.RigidShapeType;
+import jp.sourceforge.mikutoga.pmd.TripletRange;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.DynamicsInfo;
+import jp.sourceforge.mikutoga.pmd.model.JointInfo;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.RigidGroup;
+import jp.sourceforge.mikutoga.pmd.model.RigidInfo;
+import jp.sourceforge.mikutoga.pmd.model.RigidShape;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+
+/**
+ * PMDファイルのエクスポーター(拡張3:物理演算対応)。
+ * <p>
+ * 物理演算対応のPMDファイルフォーマットを
+ * 使いたい場合はこのエクスポーターを用いて出力せよ。
+ */
+public class PmdExporterExt3 extends PmdExporterExt2{
+
+    private static final short MASK_FULLCOLLISION = (short) 0xffff;
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     * @throws NullPointerException 引数がnull
+     */
+    public PmdExporterExt3(OutputStream stream)
+            throws NullPointerException{
+        super(stream);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param model {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     * @throws IllegalPmdDataException {@inheritDoc}
+     */
+    @Override
+    public void dumpPmdModel(PmdModel model)
+            throws IOException, IllegalPmdDataException{
+        super.dumpPmdModel(model);
+
+        try{
+            dumpRigidList(model);
+            dumpJointList(model);
+        }catch(IllegalTextExportException e){
+            throw new IllegalPmdDataException(e);
+        }
+
+        return;
+    }
+
+    /**
+     * 剛体リストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 長すぎる剛体名
+     */
+    private void dumpRigidList(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        List<RigidInfo> rigidList = model.getRigidList();
+        int rigidNum = rigidList.size();
+        dumpInt(rigidNum);
+
+        for(RigidInfo rigid : rigidList){
+            dumpRigid(rigid);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 個別の剛体情報を出力する。
+     * @param rigid 剛体
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 長すぎる剛体名
+     */
+    private void dumpRigid(RigidInfo rigid)
+            throws IOException, IllegalTextExportException{
+        String rigidName = rigid.getRigidName().getPrimaryText();
+        dumpText(rigidName, PmdLimits.MAXBYTES_RIGIDNAME);
+
+        BoneInfo linkedBone = rigid.getLinkedBone();
+        if(linkedBone == null){
+            dumpShort(-1);
+        }else{
+            dumpShort(linkedBone.getSerialNumber());
+        }
+
+        RigidGroup group = rigid.getRigidGroup();
+        dumpByte(group.getSerialNumber());
+
+        short mask = MASK_FULLCOLLISION;
+        for(RigidGroup throughGroup : rigid.getThroughGroupColl()){
+            int serialId = throughGroup.getSerialNumber();
+            mask &= ~(0x0001 << serialId);
+        }
+        dumpShort(mask);
+
+        dumpRigidShape(rigid.getRigidShape());
+
+        dumpPos3D(rigid.getPosition());
+        dumpRad3d(rigid.getRotation());
+
+        dumpDynamics(rigid.getDynamicsInfo());
+
+        dumpByte(rigid.getBehaviorType().encode());
+
+        return;
+    }
+
+    /**
+     * 剛体形状を出力する。
+     * @param shape 剛体形状
+     * @throws IOException 出力エラー
+     */
+    private void dumpRigidShape(RigidShape shape)
+            throws IOException{
+        RigidShapeType type = shape.getShapeType();
+        dumpByte(type.encode());
+
+        float width = shape.getWidth();
+        float height = shape.getHeight();
+        float depth = shape.getDepth();
+
+        dumpFloat(width);
+        dumpFloat(height);
+        dumpFloat(depth);
+
+        return;
+    }
+
+    /**
+     * 力学設定を出力する。
+     * @param dynamics 力学設定
+     * @throws IOException 出力エラー
+     */
+    private void dumpDynamics(DynamicsInfo dynamics)
+            throws IOException{
+        float mass        = dynamics.getMass();
+        float dampPos     = dynamics.getDampingPosition();
+        float dampRot     = dynamics.getDampingRotation();
+        float restitution = dynamics.getRestitution();
+        float friction    = dynamics.getFriction();
+
+        dumpFloat(mass);
+        dumpFloat(dampPos);
+        dumpFloat(dampRot);
+        dumpFloat(restitution);
+        dumpFloat(friction);
+
+        return;
+    }
+
+    /**
+     * ジョイントリストを出力する。
+     * @param model モデルデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 長すぎるジョイント名
+     */
+    private void dumpJointList(PmdModel model)
+            throws IOException, IllegalTextExportException{
+        List<JointInfo> jointList = model.getJointList();
+        int jointNum = jointList.size();
+        dumpInt(jointNum);
+
+        for(JointInfo joint : jointList){
+            dumpJoint(joint);
+        }
+
+        flush();
+
+        return;
+    }
+
+    /**
+     * 個別のジョイント情報を出力する。
+     * @param joint ジョイント
+     * @throws IOException 出力エラー
+     * @throws IllegalPmdTextException 長すぎるジョイント名
+     */
+    private void dumpJoint(JointInfo joint)
+            throws IOException, IllegalTextExportException{
+        String jointName = joint.getJointName().getPrimaryText();
+        dumpText(jointName, PmdLimits.MAXBYTES_JOINTNAME);
+
+        RigidInfo rigidA = joint.getRigidA();
+        RigidInfo rigidB = joint.getRigidB();
+
+        dumpInt(rigidA.getSerialNumber());
+        dumpInt(rigidB.getSerialNumber());
+
+        dumpPos3D(joint.getPosition());
+        dumpRad3d(joint.getRotation());
+
+        dumpTripletRange(joint.getPositionRange());
+        dumpTripletRange(joint.getRotationRange());
+
+        dumpPos3D(joint.getElasticPosition());
+        dumpDeg3d(joint.getElasticRotation());
+
+        return;
+    }
+
+    /**
+     * 3次元範囲制約を出力する。
+     * @param range 3次元範囲制約
+     * @throws IOException 出力エラー
+     */
+    protected void dumpTripletRange(TripletRange range) throws IOException{
+        float xFrom = range.getXFrom();
+        float yFrom = range.getYFrom();
+        float zFrom = range.getZFrom();
+
+        dumpFloat(xFrom);
+        dumpFloat(yFrom);
+        dumpFloat(zFrom);
+
+        float xTo = range.getXTo();
+        float yTo = range.getYTo();
+        float zTo = range.getZTo();
+
+        dumpFloat(xTo);
+        dumpFloat(yTo);
+        dumpFloat(zTo);
+
+        return;
+    }
+
+    /**
+     * ラジアンによる3次元姿勢情報を出力する。
+     * @param rad 3次元姿勢情報
+     * @throws IOException 出力エラー
+     */
+    protected void dumpRad3d(Rad3d rad) throws IOException{
+        float xVal = rad.getXRad();
+        float yVal = rad.getYRad();
+        float zVal = rad.getZRad();
+
+        dumpFloat(xVal);
+        dumpFloat(yVal);
+        dumpFloat(zVal);
+
+        return;
+    }
+
+    /**
+     * 度数法による3次元姿勢情報を出力する。
+     * @param deg 3次元姿勢情報
+     * @throws IOException 出力エラー
+     */
+    protected void dumpDeg3d(Deg3d deg) throws IOException{
+        float xVal = deg.getXDeg();
+        float yVal = deg.getYDeg();
+        float zVal = deg.getZDeg();
+
+        dumpFloat(xVal);
+        dumpFloat(yVal);
+        dumpFloat(zVal);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdLoader.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/PmdLoader.java
new file mode 100644 (file)
index 0000000..921cfed
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * PMD file loader
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.MmdSource;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.parser.PmdParser;
+
+/**
+ * PMDモデルファイルを読み込むためのローダ。
+ */
+public class PmdLoader {
+
+    private PmdModel model;
+    private PmdParser parser;
+    private TextBuilder textBuilder;
+
+    private boolean loaded = false;
+    private boolean hasMoreData = false;
+
+    /**
+     * コンストラクタ。
+     * @param source PMDファイル入力ソース
+     */
+    public PmdLoader(MmdSource source){
+        super();
+
+        this.model = new PmdModel();
+        this.parser = new PmdParser(source);
+        this.textBuilder = new TextBuilder(this.model);
+
+        setHandler();
+
+        return;
+    }
+
+    /**
+     * パーサに各種ハンドラの設定を行う。
+     */
+    private void setHandler(){
+        ShapeBuilder    shapeBuilder    = new ShapeBuilder(this.model);
+        MaterialBuilder materialBuilder = new MaterialBuilder(this.model);
+        BoneBuilder     boneBuilder     = new BoneBuilder(this.model);
+        MorphBuilder    morphBuilder    = new MorphBuilder(this.model);
+        ToonBuilder     toonBuilder     = new ToonBuilder(this.model);
+        RigidBuilder    rigidBuilder    = new RigidBuilder(this.model);
+        JointBuilder    jointBuilder    = new JointBuilder(this.model);
+
+        this.parser.setBasicHandler(this.textBuilder);
+        this.parser.setShapeHandler(shapeBuilder);
+        this.parser.setMaterialHandler(materialBuilder);
+        this.parser.setBoneHandler(boneBuilder);
+        this.parser.setMorphHandler(morphBuilder);
+        this.parser.setEngHandler(this.textBuilder);
+        this.parser.setToonHandler(toonBuilder);
+        this.parser.setRigidHandler(rigidBuilder);
+        this.parser.setJointHandler(jointBuilder);
+
+        List<MorphPart> morphPartList = new ArrayList<MorphPart>();
+        morphBuilder.setMorphPartList(morphPartList);
+        this.textBuilder.setMorphPartList(morphPartList);
+        morphPartList.clear();
+
+        return;
+    }
+
+    /**
+     * PMDファイルの読み込みを行いモデル情報を返す。
+     * 1インスタンスにつき一度しかロードできない。
+     * @return モデル情報
+     * @throws IOException 入力エラー
+     * @throws MmdFormatException PMDファイルフォーマットの異常を検出
+     * @throws IllegalStateException このインスタンスで再度のロードを試みた。
+     */
+    public PmdModel load()
+            throws IOException,
+                   MmdFormatException,
+                   IllegalStateException {
+        if(this.loaded) throw new IllegalStateException();
+
+        PmdModel result;
+        try{
+            this.parser.parsePmd();
+        }finally{
+            this.loaded = true;
+
+            result = this.model;
+            this.hasMoreData = this.textBuilder.hasMoreData();
+
+            this.model = null;
+            this.parser = null;
+            this.textBuilder = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * ロード処理が正常終了したのにまだ読み込んでいない部分が放置されているか判定する。
+     * MMDでの仕様拡張によるPMDファイルフォーマットの拡張が行われた場合を想定。
+     * @return 読み込んでいない部分があればtrue
+     */
+    public boolean hasMoreData(){
+        return this.hasMoreData;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/RigidBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/RigidBuilder.java
new file mode 100644 (file)
index 0000000..caeb862
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * building rigid information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.RigidBehaviorType;
+import jp.sourceforge.mikutoga.pmd.RigidShapeType;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.DynamicsInfo;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.RigidGroup;
+import jp.sourceforge.mikutoga.pmd.model.RigidInfo;
+import jp.sourceforge.mikutoga.pmd.model.RigidShape;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+import jp.sourceforge.mikutoga.pmd.parser.PmdRigidHandler;
+
+/**
+ * 剛体関係の通知をパーサから受け取る。
+ */
+class RigidBuilder implements PmdRigidHandler {
+
+    private final List<BoneInfo> boneList;
+
+    private final List<RigidInfo> rigidList;
+    private Iterator<RigidInfo> rigidIt;
+    private RigidInfo currentRigid = null;
+
+    private final List<RigidGroup> rigidGroupList;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    RigidBuilder(PmdModel model){
+        super();
+        this.boneList = model.getBoneList();
+        this.rigidList = model.getRigidList();
+        this.rigidGroupList = model.getRigidGroupList();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        ListUtil.prepareDefConsList(this.rigidList, RigidInfo.class, loops);
+        ListUtil.assignIndexedSerial(this.rigidList);
+
+        this.rigidIt = this.rigidList.iterator();
+        if(this.rigidIt.hasNext()){
+            this.currentRigid = this.rigidIt.next();
+        }
+
+        ListUtil.prepareDefConsList(this.rigidGroupList,
+                                    RigidGroup.class,
+                                    PmdLimits.RIGIDGROUP_FIXEDNUM );
+        ListUtil.assignIndexedSerial(this.rigidGroupList);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        if(this.rigidIt.hasNext()){
+            this.currentRigid = this.rigidIt.next();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rigidName {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidName(String rigidName){
+        this.currentRigid.getRigidName().setPrimaryText(rigidName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rigidGroupId {@inheritDoc}
+     * @param linkedBoneId {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidInfo(int rigidGroupId, int linkedBoneId){
+        BoneInfo bone;
+        if(linkedBoneId < 0 || 65535 <= linkedBoneId){
+            bone = null;
+        }else{
+            bone = this.boneList.get(linkedBoneId);
+        }
+        RigidGroup group = this.rigidGroupList.get(rigidGroupId);
+
+        this.currentRigid.setLinkedBone(bone);
+        this.currentRigid.setRigidGroup(group);
+        group.getRigidList().add(this.currentRigid);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param shapeType {@inheritDoc}
+     * @param width {@inheritDoc}
+     * @param height {@inheritDoc}
+     * @param depth {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidShape(byte shapeType,
+                              float width, float height, float depth){
+        RigidShape shape = this.currentRigid.getRigidShape();
+
+        shape.setWidth(width);
+        shape.setHeight(height);
+        shape.setDepth(depth);
+
+        RigidShapeType type = RigidShapeType.decode(shapeType);
+        shape.setShapeType(type);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param posX {@inheritDoc}
+     * @param posY {@inheritDoc}
+     * @param posZ {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidPosition(float posX, float posY, float posZ){
+        MkPos3D position = this.currentRigid.getPosition();
+        position.setXpos(posX);
+        position.setYpos(posY);
+        position.setZpos(posZ);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param radX {@inheritDoc}
+     * @param radY {@inheritDoc}
+     * @param radZ {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidRotation(float radX, float radY, float radZ){
+        Rad3d rotation = this.currentRigid.getRotation();
+        rotation.setXRad(radX);
+        rotation.setYRad(radY);
+        rotation.setZRad(radZ);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param mass {@inheritDoc}
+     * @param dampingPos {@inheritDoc}
+     * @param dampingRot {@inheritDoc}
+     * @param restitution {@inheritDoc}
+     * @param friction {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidPhysics(float mass,
+                                  float dampingPos,
+                                  float dampingRot,
+                                  float restitution,
+                                  float friction ){
+        DynamicsInfo info = this.currentRigid.getDynamicsInfo();
+
+        info.setMass(mass);
+        info.setDampingPosition(dampingPos);
+        info.setDampingRotation(dampingRot);
+        info.setRestitution(restitution);
+        info.setFriction(friction);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param behaveType {@inheritDoc}
+     * @param collisionMap {@inheritDoc}
+     */
+    @Override
+    public void pmdRigidBehavior(byte behaveType, short collisionMap){
+        RigidBehaviorType type = RigidBehaviorType.decode(behaveType);
+        this.currentRigid.setBehaviorType(type);
+
+        for(int bitPos = 0; bitPos < PmdLimits.RIGIDGROUP_FIXEDNUM; bitPos++){
+            short mask = 0x0001;
+            mask <<= bitPos;
+            if((collisionMap & mask) == 0){
+                RigidGroup group = this.rigidGroupList.get(bitPos);
+                this.currentRigid.getThroughGroupColl().add(group);
+            }
+        }
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/ShapeBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/ShapeBuilder.java
new file mode 100644 (file)
index 0000000..02821f5
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * building shape information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.RandomAccess;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.math.MkPos2D;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkVec3D;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.Surface;
+import jp.sourceforge.mikutoga.pmd.model.Vertex;
+import jp.sourceforge.mikutoga.pmd.parser.PmdShapeHandler;
+
+/**
+ * モデル形状に関する通知をパーサから受け取る。
+ */
+class ShapeBuilder implements PmdShapeHandler {
+
+    private final List<Vertex> vertexList;
+    private final List<BoneInfo> boneList;
+    private final List<Surface> surfaceList;
+
+    private Iterator<Vertex> vertexIt;
+    private Vertex currentVertex = null;
+
+    private Iterator<Surface> surfaceIt;
+    private Surface currentSurface = null;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    ShapeBuilder(PmdModel model){
+        super();
+
+        this.vertexList  = model.getVertexList();
+        this.boneList    = model.getBoneList();
+        this.surfaceList = model.getSurfaceList();
+
+        assert this.vertexList instanceof RandomAccess;
+        assert this.surfaceList instanceof RandomAccess;
+        assert this.boneList instanceof RandomAccess;
+
+        return;
+    }
+
+    /**
+     * ボーンリスト上にボーンを用意する。
+     * すでに指定位置にボーンがあればなにもしない。
+     * @param id 0から始まるボーン番号
+     * @return 用意されたボーン
+     */
+    private BoneInfo prepareBone(int id){
+        ListUtil.extendList(this.boneList, id + 1);
+        BoneInfo bone = this.boneList.get(id);
+        if(bone == null){
+            bone = new BoneInfo();
+            bone.setSerialNumber(id);
+            this.boneList.set(id, bone);
+        }
+        return bone;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        if(stage == PmdShapeHandler.VERTEX_LIST){
+            ListUtil.prepareDefConsList(this.vertexList, Vertex.class, loops);
+            ListUtil.assignIndexedSerial(this.vertexList);
+
+            this.vertexIt = this.vertexList.iterator();
+            if(this.vertexIt.hasNext()){
+                this.currentVertex = this.vertexIt.next();
+            }
+        }else if(stage == PmdShapeHandler.SURFACE_LIST){
+            ListUtil.prepareDefConsList(this.surfaceList,
+                                        Surface.class, loops );
+            ListUtil.assignIndexedSerial(this.surfaceList);
+
+            this.surfaceIt = this.surfaceList.iterator();
+            if(this.surfaceIt.hasNext()){
+                this.currentSurface = this.surfaceIt.next();
+            }
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        if(stage == PmdShapeHandler.VERTEX_LIST){
+            if(this.vertexIt.hasNext()){
+                this.currentVertex = this.vertexIt.next();
+            }
+        }else if(stage == PmdShapeHandler.SURFACE_LIST){
+            if(this.surfaceIt.hasNext()){
+                this.currentSurface = this.surfaceIt.next();
+            }
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xPos {@inheritDoc}
+     * @param yPos {@inheritDoc}
+     * @param zPos {@inheritDoc}
+     */
+    @Override
+    public void pmdVertexPosition(float xPos, float yPos, float zPos){
+        MkPos3D position = this.currentVertex.getPosition();
+        position.setXpos(xPos);
+        position.setYpos(yPos);
+        position.setZpos(zPos);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xVec {@inheritDoc}
+     * @param yVec {@inheritDoc}
+     * @param zVec {@inheritDoc}
+     */
+    @Override
+    public void pmdVertexNormal(float xVec, float yVec, float zVec){
+        MkVec3D normal = this.currentVertex.getNormal();
+        normal.setXVal(xVec);
+        normal.setYVal(yVec);
+        normal.setZVal(zVec);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param uVal {@inheritDoc}
+     * @param vVal {@inheritDoc}
+     */
+    @Override
+    public void pmdVertexUV(float uVal, float vVal){
+        MkPos2D uv = this.currentVertex.getUVPosition();
+        uv.setXpos(uVal);
+        uv.setYpos(vVal);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param boneId1 {@inheritDoc}
+     * @param boneId2 {@inheritDoc}
+     * @param weightForB1 {@inheritDoc}
+     */
+    @Override
+    public void pmdVertexWeight(int boneId1, int boneId2, int weightForB1){
+        BoneInfo bone1 = prepareBone(boneId1);
+        BoneInfo bone2 = prepareBone(boneId2);
+
+        this.currentVertex.setBonePair(bone1, bone2);
+        this.currentVertex.setWeightA(weightForB1);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param hideEdge {@inheritDoc}
+     */
+    @Override
+    public void pmdVertexEdge(boolean hideEdge){
+        this.currentVertex.setEdgeAppearance( ! hideEdge );
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param vertexId1 {@inheritDoc}
+     * @param vertexId2 {@inheritDoc}
+     * @param vertexId3 {@inheritDoc}
+     */
+    @Override
+    public void pmdSurfaceTriangle(int vertexId1,
+                                      int vertexId2,
+                                      int vertexId3 ){
+        Vertex vtx1 = this.vertexList.get(vertexId1);
+        Vertex vtx2 = this.vertexList.get(vertexId2);
+        Vertex vtx3 = this.vertexList.get(vertexId3);
+
+        this.currentSurface.setTriangle(vtx1, vtx2, vtx3);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/TextBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/TextBuilder.java
new file mode 100644 (file)
index 0000000..51c0ac8
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * building text info
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import java.util.Iterator;
+import java.util.List;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.parser.PmdBasicHandler;
+import jp.sourceforge.mikutoga.pmd.parser.PmdEngHandler;
+
+/**
+ * テキスト関係の通知をパーサから受け取る。
+ */
+class TextBuilder implements PmdBasicHandler, PmdEngHandler {
+
+    private final PmdModel model;
+
+    private final I18nText modelName;
+    private final I18nText description;
+
+    private final List<BoneInfo> boneList;
+    private Iterator<BoneInfo> boneIt;
+    private BoneInfo currentBone = null;
+
+    private List<MorphPart> morphPartList;
+    private Iterator<MorphPart> morphPartIt;
+    private MorphPart currentMorphPart = null;
+
+    private final List<BoneGroup> boneGroupList;
+    private Iterator<BoneGroup> boneGroupIt;
+    private BoneGroup currentBoneGroup = null;
+
+    private boolean hasMoreData = false;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    TextBuilder(PmdModel model){
+        super();
+
+        this.model = model;
+
+        this.modelName   = model.getModelName();
+        this.description = model.getDescription();
+
+        this.boneList      = model.getBoneList();
+        this.boneGroupList = model.getBoneGroupList();
+
+        return;
+    }
+
+    /**
+     * PMDファイル中の出現順で各モーフを格納するためのリストを設定する。
+     * 主な用途はモーフ和英名の突き合わせ作業。
+     * @param list モーフ格納リスト
+     */
+    void setMorphPartList(List<MorphPart> list){
+        this.morphPartList = list;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void pmdParseStart(){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param hasMoreData {@inheritDoc}
+     */
+    @Override
+    public void pmdParseEnd(boolean hasMoreData){
+        this.hasMoreData = hasMoreData;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        if(stage == PmdEngHandler.ENGBONE_LIST){
+            this.boneIt = this.boneList.iterator();
+            if(this.boneIt.hasNext()){
+                this.currentBone = this.boneIt.next();
+            }
+        }else if(stage == PmdEngHandler.ENGMORPH_LIST){
+            this.morphPartIt = this.morphPartList.iterator();
+
+            // 「base」モーフを読み飛ばす
+            assert this.morphPartIt.hasNext();
+            MorphPart part = this.morphPartIt.next();
+            assert part != null;
+
+            if(this.morphPartIt.hasNext()){
+                this.currentMorphPart = this.morphPartIt.next();
+            }
+        }else if(stage == PmdEngHandler.ENGBONEGROUP_LIST){
+            this.boneGroupIt = this.boneGroupList.iterator();
+
+            // デフォルトボーングループを読み飛ばす
+            assert this.boneGroupIt.hasNext();
+            BoneGroup group = this.boneGroupIt.next();
+            assert group != null;
+
+            if(this.boneGroupIt.hasNext()){
+                this.currentBoneGroup = this.boneGroupIt.next();
+            }
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        if(stage == PmdEngHandler.ENGBONE_LIST){
+            if(this.boneIt.hasNext()){
+                this.currentBone = this.boneIt.next();
+            }
+        }else if(stage == PmdEngHandler.ENGMORPH_LIST){
+            if(this.morphPartIt.hasNext()){
+                this.currentMorphPart = this.morphPartIt.next();
+            }
+        }else if(stage == PmdEngHandler.ENGBONEGROUP_LIST){
+            if(this.boneGroupIt.hasNext()){
+                this.currentBoneGroup = this.boneGroupIt.next();
+            }
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param header {@inheritDoc}
+     */
+    @Override
+    public void pmdHeaderInfo(byte[] header){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param modelName {@inheritDoc}
+     * @param description {@inheritDoc}
+     */
+    @Override
+    public void pmdModelInfo(String modelName, String description){
+        this.modelName  .setPrimaryText(modelName);
+        this.description.setPrimaryText(description);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param hasEnglishInfo {@inheritDoc}
+     */
+    @Override
+    public void pmdEngEnabled(boolean hasEnglishInfo){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param modelName {@inheritDoc}
+     * @param description {@inheritDoc}
+     */
+    @Override
+    public void pmdEngModelInfo(String modelName, String description){
+        this.modelName  .setGlobalText(modelName);
+        this.description.setGlobalText(description);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param boneName {@inheritDoc}
+     */
+    @Override
+    public void pmdEngBoneInfo(String boneName){
+        this.currentBone.getBoneName().setGlobalText(boneName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param morphName {@inheritDoc}
+     */
+    @Override
+    public void pmdEngMorphInfo(String morphName){
+        this.currentMorphPart.getMorphName().setGlobalText(morphName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param groupName {@inheritDoc}
+     */
+    @Override
+    public void pmdEngBoneGroupInfo(String groupName){
+        this.currentBoneGroup.getGroupName().setGlobalText(groupName);
+        return;
+    }
+
+    /**
+     * 読み残したデータがあるか判定する。
+     * @return 読み残したデータがあればtrue
+     */
+    public boolean hasMoreData(){
+        return this.hasMoreData;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/ToonBuilder.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/ToonBuilder.java
new file mode 100644 (file)
index 0000000..a143d80
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * building toon information
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.ToonMap;
+import jp.sourceforge.mikutoga.pmd.parser.PmdLimits;
+import jp.sourceforge.mikutoga.pmd.parser.PmdToonHandler;
+
+/**
+ * トゥーン関係の通知をパーサから受け取る。
+ */
+class ToonBuilder implements PmdToonHandler {
+
+    private final PmdModel model;
+
+    private ToonMap toonMap;
+    private int index;
+
+    /**
+     * コンストラクタ。
+     * @param model モデル
+     */
+    ToonBuilder(PmdModel model){
+        this.model = model;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops){
+        assert stage == PmdToonHandler.TOON_LIST;
+        assert loops == PmdLimits.TOON_FIXEDNUM;
+
+        this.toonMap = new ToonMap();
+        this.index = 0;
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage){
+        assert stage == PmdToonHandler.TOON_LIST;
+        this.index++;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage){
+        assert stage == PmdToonHandler.TOON_LIST;
+        this.model.setToonMap(this.toonMap);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param toonFileName {@inheritDoc}
+     */
+    @Override
+    public void pmdToonFileInfo(String toonFileName){
+        this.toonMap.setIndexedToon(this.index, toonFileName);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/package-info.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/binio/package-info.java
new file mode 100644 (file)
index 0000000..09c41bc
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+/**
+ * PMDモデルファイル(*.pmd)用入出力。
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.binio;
+
+/* EOF */
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/PmdXmlExporter.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/PmdXmlExporter.java
new file mode 100644 (file)
index 0000000..705a67c
--- /dev/null
@@ -0,0 +1,1294 @@
+/*
+ * pmd-xml exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.xml;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.math.MkPos2D;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkVec3D;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+import jp.sourceforge.mikutoga.pmd.Deg3d;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.RigidShapeType;
+import jp.sourceforge.mikutoga.pmd.TripletRange;
+import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.DynamicsInfo;
+import jp.sourceforge.mikutoga.pmd.model.IKChain;
+import jp.sourceforge.mikutoga.pmd.model.JointInfo;
+import jp.sourceforge.mikutoga.pmd.model.Material;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.RigidGroup;
+import jp.sourceforge.mikutoga.pmd.model.RigidInfo;
+import jp.sourceforge.mikutoga.pmd.model.RigidShape;
+import jp.sourceforge.mikutoga.pmd.model.SerialNumbered;
+import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
+import jp.sourceforge.mikutoga.pmd.model.Surface;
+import jp.sourceforge.mikutoga.pmd.model.ToonMap;
+import jp.sourceforge.mikutoga.pmd.model.Vertex;
+import jp.sourceforge.mikutoga.xml.BasicXmlExporter;
+import jp.sourceforge.mikutoga.xml.XmlResourceResolver;
+
+/**
+ * XML形式でPMDモデルデータを出力する。
+ */
+public class PmdXmlExporter extends BasicXmlExporter{
+
+    private static final String TOP_COMMENT =
+            "  MikuMikuDance\n    model-data(*.pmd) on XML";
+    private static final String SCHEMA_LOCATION =
+            PmdXmlResources.NS_PMDXML + " " + PmdXmlResources.SCHEMA_PMDXML;
+
+    /** 改行文字列 CR。 */
+    private static final String CR = "\r";       // 0x0d
+    /** 改行文字列 LF。 */
+    private static final String LF = "\n";       // 0x0a
+    /** 改行文字列 CRLF。 */
+    private static final String CRLF = CR + LF;  // 0x0d, 0x0a
+
+    private static final String PFX_SURFACEGROUP = "sg";
+    private static final String PFX_TOONFILE = "tf";
+    private static final String PFX_VERTEX = "vtx";
+    private static final String PFX_BONE = "bn";
+    private static final String PFX_RIGID = "rd";
+    private static final String PFX_RIGIDGROUP = "rg";
+
+    private static final String BONETYPE_COMMENT =
+          "Bone types:\n"
+        + "[0 : ROTATE      : Rotate       : 回転           :]\n"
+        + "[1 : ROTMOV      : Rotate/Move  : 回転/移動      :]\n"
+        + "[2 : IK          : IK           : IK             :]\n"
+        + "[3 : UNKNOWN     : Unknown      : 不明           :]\n"
+        + "[4 : UNDERIK     : Under IK     : IK影響下(回転) :]\n"
+        + "[5 : UNDERROT    : Under rotate : 回転影響下     :]\n"
+        + "[6 : IKCONNECTED : IK connected : IK接続先       :]\n"
+        + "[7 : HIDDEN      : Hidden       : 非表示         :]\n"
+        + "[8 : TWIST       : Twist        : 捩り           :]\n"
+        + "[9 : LINKEDROT   : Linked Rotate: 回転連動       :]\n";
+
+    private static final String MORPHTYPE_COMMENT =
+          "Morph types:\n"
+        + "[1 : EYEBROW : まゆ   ]\n"
+        + "[2 : EYE     : 目     ]\n"
+        + "[3 : LIP     : リップ ]\n"
+        + "[4 : EXTRA   : その他 ]\n";
+
+    private static final String RIGIDBEHAVIOR_COMMENT =
+          "Rigid behavior types:\n"
+        + "[0 : FOLLOWBONE    : ボーン追従       ]\n"
+        + "[1 : ONLYDYNAMICS  : 物理演算         ]\n"
+        + "[2 : BONEDDYNAMICS : ボーン位置合わせ ]\n";
+
+    private String generator = "";
+
+    /**
+     * コンストラクタ。
+     * 文字エンコーディングはUTF-8が用いられる。
+     * @param stream 出力ストリーム
+     */
+    public PmdXmlExporter(OutputStream stream){
+        super(stream);
+        return;
+    }
+
+    /**
+     * Generatorメタ情報を設定する。
+     * @param generatorArg Generatorメタ情報
+     * @throws NullPointerException 引数がnull
+     */
+    public void setGenerator(String generatorArg)
+            throws NullPointerException{
+        if(generatorArg == null) throw new NullPointerException();
+        this.generator = generatorArg;
+        return;
+    }
+
+    /**
+     * 任意の文字列がBasicLatin文字のみから構成されるか判定する。
+     * @param seq 文字列
+     * @return null、長さ0もしくはBasicLatin文字のみから構成されるならtrue
+     */
+    public static boolean hasOnlyBasicLatin(CharSequence seq){
+        if(seq == null) return true;
+        int length = seq.length();
+        for(int pos = 0; pos < length; pos++){
+            char ch = seq.charAt(pos);
+            if(ch > 0x007f) return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    public PmdXmlExporter ind() throws IOException{
+        super.ind();
+        return this;
+    }
+
+    /**
+     * 文字参照によるエスケープを補佐するためのコメントを出力する。
+     * @param seq 文字列
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putUnescapedComment(CharSequence seq)
+            throws IOException{
+        if( ! isBasicLatinOnlyOut() ) return this;
+        if(hasOnlyBasicLatin(seq)) return this;
+        sp().putLineComment(seq);
+        return this;
+    }
+
+    /**
+     * 多言語化された各種識別名を出力する。
+     * プライマリ名は出力対象外。
+     * @param text 多言語文字列
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putI18nName(I18nText text) throws IOException{
+        for(String lang639 : text.lang639CodeList()){
+            if(lang639.equals(I18nText.CODE639_PRIMARY)) continue;
+            String name = text.getI18nText(lang639);
+            ind().put("<i18nName ");
+            putAttr("lang", lang639).sp();
+            putAttr("name", name);
+            put(" />");
+            putUnescapedComment(name);
+            ln();
+        }
+        return this;
+    }
+
+    /**
+     * 番号付けされたID(IDREF)属性を出力する。
+     * @param attrName 属性名
+     * @param prefix IDプレフィクス
+     * @param num 番号
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putNumberedIdAttr(CharSequence attrName,
+                                                 CharSequence prefix,
+                                                 int num )
+            throws IOException{
+        put(attrName).put("=\"");
+        put(prefix).put(num);
+        put('"');
+        return this;
+    }
+
+    /**
+     * 番号付けされたID(IDREF)属性を出力する。
+     * @param attrName 属性名
+     * @param prefix IDプレフィクス
+     * @param numbered 番号付けされたオブジェクト
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putNumberedIdAttr(CharSequence attrName,
+                                                 CharSequence prefix,
+                                                 SerialNumbered numbered )
+            throws IOException{
+        putNumberedIdAttr(attrName, prefix, numbered.getSerialNumber());
+        return this;
+    }
+
+    /**
+     * 位置情報を出力する。
+     * @param position 位置情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putPosition(MkPos3D position)
+            throws IOException{
+        put("<position ");
+        putFloatAttr("x", (float) position.getXpos()).sp();
+        putFloatAttr("y", (float) position.getYpos()).sp();
+        putFloatAttr("z", (float) position.getZpos()).sp();
+        put("/>");
+        return this;
+    }
+
+    /**
+     * 姿勢情報(ラジアン)を出力する。
+     * @param rotation 姿勢情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putRadRotation(Rad3d rotation)
+            throws IOException{
+        put("<radRotation ");
+        putFloatAttr("xRad", rotation.getXRad()).sp();
+        putFloatAttr("yRad", rotation.getYRad()).sp();
+        putFloatAttr("zRad", rotation.getZRad()).sp();
+        put("/>");
+        return this;
+    }
+
+    /**
+     * 多言語識別名属性のローカルな名前をコメント出力する。
+     * @param name 多言語識別名
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putLocalNameComment(I18nText name)
+            throws IOException{
+        String localName = name.getText();
+        ind().putLineComment(localName);
+        return this;
+    }
+
+    /**
+     * 多言語識別名属性のプライマリな名前を出力する。
+     * @param attrName 属性名
+     * @param name 多言語識別名
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected PmdXmlExporter putPrimaryNameAttr(CharSequence attrName,
+                                                   I18nText name)
+            throws IOException{
+        String primaryName = name.getPrimaryText();
+        putAttr(attrName, primaryName);
+        return this;
+    }
+
+    /**
+     * PMDモデルデータをXML形式で出力する。
+     * @param model PMDモデルデータ
+     * @throws IOException 出力エラー
+     */
+    public void putPmdModel(PmdModel model) throws IOException{
+        ind().put("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>").ln(2);
+
+        ind().putBlockComment(TOP_COMMENT).ln(2);
+
+        /*
+        ind().put("<!DOCTYPE pmdModel").ln();
+        ind().put(" SYSTEM \"")
+             .put(PmdXmlResources.DTD_PMDXML)
+             .put("\" >")
+             .ln(3);
+         */
+
+        I18nText modelName = model.getModelName();
+        ind().putLocalNameComment(modelName).ln();
+        ind().put("<pmdModel").ln();
+        pushNest();
+        ind().putAttr("xmlns", PmdXmlResources.NS_PMDXML).ln();
+        ind().putAttr("xmlns:xsi", XmlResourceResolver.NS_XSD).ln();
+        ind().putAttr("xsi:schemaLocation", SCHEMA_LOCATION).ln();
+        ind().putAttr("schemaVersion", PmdXmlResources.VER_PMDXML).ln(2);
+        ind().putPrimaryNameAttr("name", modelName).ln();
+        popNest();
+        put(">").ln(2);
+
+        putModelInfo(model).flush();
+        putMetaInfo(model).flush();
+        putMaterialList(model).flush();
+        putToonMap(model).flush();
+        putBoneList(model).flush();
+        putBoneGroupList(model).flush();
+        putIKChainList(model).flush();
+        putMorphList(model).flush();
+        putRigidList(model).flush();
+        putRigidGroupList(model).flush();
+        putJointList(model).flush();
+        putSurfaceGroupList(model).flush();
+        putVertexList(model).flush();
+
+        ind().put("</pmdModel>").ln(2);
+        ind().put("<!-- EOF -->").ln();
+
+        return;
+    }
+
+    /**
+     * モデル基本情報を出力する。
+     * @param model モデル情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putModelInfo(PmdModel model)
+            throws IOException{
+        I18nText modelName = model.getModelName();
+        putI18nName(modelName);
+        ln();
+
+        I18nText description = model.getDescription();
+        for(String lang639 : description.lang639CodeList()){
+            String descText = description.getI18nText(lang639);
+            putDescription(lang639, descText);
+            ln();
+        }
+
+        return this;
+    }
+
+    /**
+     * モデル詳細テキストを出力する。
+     * @param lang639 言語コード
+     * @param content 詳細内容
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putDescription(CharSequence lang639,
+                                              CharSequence content)
+            throws IOException{
+        String text = content.toString();
+        text = text.replace(CRLF, LF);
+        text = text.replace(CR,   LF);
+
+        ind().put("<description");
+        if( ! I18nText.CODE639_PRIMARY.equals(lang639) ){
+            sp();
+            putAttr("lang", lang639);
+        }
+        put(">").ln();
+
+        putBRedContent(text);
+
+        ln();
+        ind().put("</description>").ln();
+
+        if( ! hasOnlyBasicLatin(text) && isBasicLatinOnlyOut() ){
+            putBlockComment(text);
+        }
+
+        return this;
+    }
+
+    /**
+     * break要素を含む要素内容を出力する。
+     * 必要に応じてXML定義済み実体文字が割り振られた文字、
+     * コントロールコード、および非BasicLatin文字がエスケープされる。
+     * \nはbrタグに変換される。
+     * @param content 内容
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    protected BasicXmlExporter putBRedContent(CharSequence content)
+            throws IOException{
+        int length = content.length();
+
+        for(int pos = 0; pos < length; pos++){
+            char ch = content.charAt(pos);
+            if(ch == '\n'){
+                put("<br/>").ln();
+            }else if(Character.isISOControl(ch)){
+                putCharRef2Hex(ch);
+            }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){
+                putCharRef4Hex(ch);
+            }else{
+                switch(ch){
+                case '&':  put("&amp;");  break;
+                case '<':  put("&lt;");   break;
+                case '>':  put("&gt;");   break;
+                case '"':  put("&quot;"); break;
+                case '\'': put("&apos;"); break;
+                default:   put(ch);       break;
+                }
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * 各種メタ情報を出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMetaInfo(PmdModel model) throws IOException{
+        ind().put("<license>").ln();
+        ind().put("</license>").ln(2);
+
+        ind().put("<credits>").ln();
+        ind().put("</credits>").ln(2);
+
+        ind().put("<meta ");
+        putAttr("name", "generator").sp()
+                                    .putAttr("content", this.generator);
+        put(" />").ln();
+        ind().put("<meta ");
+        putAttr("name", "siteURL").sp().putAttr("content", "");
+        put(" />").ln();
+        ind().put("<meta ");
+        putAttr("name", "imageURL").sp().putAttr("content", "");
+        put(" />").ln(2);
+
+        return this;
+    }
+
+    /**
+     * マテリアル素材一覧を出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMaterialList(PmdModel model)
+            throws IOException{
+        ind().put("<materialList>").ln(2);
+        pushNest();
+
+        int ct = 0;
+        for(Material material : model.getMaterialList()){
+            putMaterial(material, ct++);
+        }
+
+        popNest();
+        ind().put("</materialList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * マテリアル素材情報を出力する。
+     * @param material マテリアル素材
+     * @param no マテリアル通し番号
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMaterial(Material material, int no)
+            throws IOException{
+        String bool;
+        if(material.getEdgeAppearance()) bool = "true";
+        else                             bool = "false";
+        I18nText name = material.getMaterialName();
+        String primary = name.getPrimaryText();
+        String local = name.getText();
+
+        if(local != null && local.length() > 0){
+            ind().putLineComment(local).ln();
+        }
+        ind().put("<material ");
+        if(primary != null && primary.length() > 0){
+            putAttr("name", primary).sp();
+        }
+
+        putAttr("showEdge", bool);
+        sp();
+        putNumberedIdAttr("surfaceGroupIdRef", PFX_SURFACEGROUP, no);
+        put('>').ln();
+        pushNest();
+
+        putI18nName(name);
+
+        float[] rgba = new float[4];
+
+        Color diffuse = material.getDiffuseColor();
+        diffuse.getRGBComponents(rgba);
+        ind().put("<diffuse ");
+        putFloatAttr("r", rgba[0]).sp();
+        putFloatAttr("g", rgba[1]).sp();
+        putFloatAttr("b", rgba[2]).sp();
+        putFloatAttr("alpha", rgba[3]).sp();
+        put("/>").ln();
+
+        Color specular = material.getSpecularColor();
+        specular.getRGBComponents(rgba);
+        float shininess = material.getShininess();
+        ind().put("<specular ");
+        putFloatAttr("r", rgba[0]).sp();
+        putFloatAttr("g", rgba[1]).sp();
+        putFloatAttr("b", rgba[2]).sp();
+        putFloatAttr("shininess", shininess).sp();
+        put("/>").ln();
+
+        Color ambient = material.getAmbientColor();
+        ambient.getRGBComponents(rgba);
+        ind().put("<ambient ");
+        putFloatAttr("r", rgba[0]).sp();
+        putFloatAttr("g", rgba[1]).sp();
+        putFloatAttr("b", rgba[2]).sp();
+        put("/>").ln();
+
+        ShadeInfo shade = material.getShadeInfo();
+        String textureFileName = shade.getTextureFileName();
+        String spheremapFileName = shade.getSpheremapFileName();
+
+        if(shade.isValidToonIndex()){
+            ind().put("<toon ");
+            int toonIdx = shade.getToonIndex();
+            putNumberedIdAttr("toonFileIdRef", PFX_TOONFILE, toonIdx);
+            put(" />");
+            String toonFileName = shade.getToonFileName();
+            if(toonFileName != null && toonFileName.length() > 0){
+                sp().putLineComment(toonFileName);
+            }
+            ln();
+        }
+
+        if(textureFileName != null && textureFileName.length() > 0){
+            ind().put("<textureFile ");
+            putAttr("winFileName", textureFileName);
+            put(" />").ln();
+        }
+
+        if(spheremapFileName != null && spheremapFileName.length() > 0){
+            ind().put("<spheremapFile ");
+            putAttr("winFileName", spheremapFileName);
+            put(" />").ln();
+        }
+
+        popNest();
+        ind().put("</material>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * トゥーンファイルマッピング情報を出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putToonMap(PmdModel model)
+            throws IOException{
+        ind().put("<toonMap>").ln();
+        pushNest();
+
+        ToonMap map = model.getToonMap();
+        for(int index = 0; index <= 9; index++){
+            ind().putToon(map, index).ln();
+        }
+
+        popNest();
+        ind().put("</toonMap>").ln(2);
+        return this;
+    }
+
+    /**
+     * 個別のトゥーンファイル情報を出力する。
+     * @param map トゥーンマップ
+     * @param index インデックス値
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putToon(ToonMap map, int index)
+            throws IOException{
+        put("<toonDef ");
+        putNumberedIdAttr("toonFileId", PFX_TOONFILE, index).sp();
+        putIntAttr("index", index).sp();
+        String toonFile = map.getIndexedToon(index);
+        putAttr("winFileName", toonFile);
+        put(" />");
+        putUnescapedComment(toonFile);
+        return this;
+    }
+
+    /**
+     * サーフェイスグループリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putSurfaceGroupList(PmdModel model)
+            throws IOException{
+        ind().put("<surfaceGroupList>").ln(2);
+        pushNest();
+
+        int ct = 0;
+        for(Material material : model.getMaterialList()){
+            List<Surface> surfaceList = material.getSurfaceList();
+            putSurfaceList(surfaceList, ct++);
+        }
+
+        popNest();
+        ind().put("</surfaceGroupList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のサーフェイスグループを出力する。
+     * @param surfaceList サーフェイスのリスト
+     * @param index グループインデックス
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putSurfaceList(List<Surface> surfaceList,
+                                              int index)
+            throws IOException{
+        ind().put("<surfaceGroup ");
+        putNumberedIdAttr("surfaceGroupId", PFX_SURFACEGROUP, index);
+        put(">").ln();
+        pushNest();
+
+        for(Surface surface : surfaceList){
+            putSurface(surface);
+        }
+
+        popNest();
+        ind().put("</surfaceGroup>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のサーフェイスを出力する。
+     * @param surface サーフェイス
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putSurface(Surface surface)
+            throws IOException{
+        ind().put("<surface ");
+
+        Vertex vertex1 = surface.getVertex1();
+        Vertex vertex2 = surface.getVertex2();
+        Vertex vertex3 = surface.getVertex3();
+
+        putNumberedIdAttr("vtxIdRef1", PFX_VERTEX, vertex1).sp();
+        putNumberedIdAttr("vtxIdRef2", PFX_VERTEX, vertex2).sp();
+        putNumberedIdAttr("vtxIdRef3", PFX_VERTEX, vertex3).sp();
+
+        put("/>").ln();
+        return this;
+    }
+
+    /**
+     * 頂点リストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putVertexList(PmdModel model)
+            throws IOException{
+        ind().put("<vertexList>").ln(2);
+        pushNest();
+
+        for(Vertex vertex : model.getVertexList()){
+            putVertex(vertex);
+        }
+
+        popNest();
+        ind().put("</vertexList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別の頂点情報を出力する。
+     * @param vertex 頂点
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putVertex(Vertex vertex)
+            throws IOException{
+        String bool;
+        if(vertex.getEdgeAppearance()) bool = "true";
+        else                           bool = "false";
+
+        ind().put("<vertex ");
+        putNumberedIdAttr("vtxId", PFX_VERTEX, vertex).sp();
+        putAttr("showEdge", bool);
+        put(">").ln();
+        pushNest();
+
+        MkPos3D position = vertex.getPosition();
+        ind().putPosition(position).ln();
+
+        MkVec3D normal = vertex.getNormal();
+        ind().put("<normal ");
+        putFloatAttr("x", (float) normal.getXVal()).sp();
+        putFloatAttr("y", (float) normal.getYVal()).sp();
+        putFloatAttr("z", (float) normal.getZVal()).sp();
+        put("/>").ln();
+
+        MkPos2D uvPos = vertex.getUVPosition();
+        ind().put("<uvMap ");
+        putFloatAttr("u", (float) uvPos.getXpos()).sp();
+        putFloatAttr("v", (float) uvPos.getYpos()).sp();
+        put("/>").ln();
+
+        BoneInfo boneA = vertex.getBoneA();
+        BoneInfo boneB = vertex.getBoneB();
+        int weight = vertex.getWeightA();
+        ind().put("<skinning ");
+        putNumberedIdAttr("boneIdRef1", PFX_BONE, boneA).sp();
+        putNumberedIdAttr("boneIdRef2", PFX_BONE, boneB).sp();
+        putIntAttr("weightBalance", weight).sp();
+        put("/>").ln();
+
+        popNest();
+        ind().put("</vertex>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * ボーンリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBoneList(PmdModel model)
+            throws IOException{
+        ind().put("<boneList>").ln(2);
+        pushNest();
+
+        putBlockComment(BONETYPE_COMMENT).ln();
+
+        for(BoneInfo bone : model.getBoneList()){
+            putBone(bone);
+        }
+
+        popNest();
+        ind().put("</boneList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のボーン情報を出力する。
+     * @param bone ボーン情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBone(BoneInfo bone)
+            throws IOException{
+        I18nText i18nName = bone.getBoneName();
+        BoneType type = bone.getBoneType();
+
+        putLocalNameComment(i18nName).putLineComment(type.getGuiName()).ln();
+        ind().put("<bone ");
+        putPrimaryNameAttr("name", i18nName).sp();
+        putNumberedIdAttr("boneId", PFX_BONE, bone).sp();
+        putAttr("type", type.name());
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        MkPos3D position = bone.getPosition();
+        ind().putPosition(position).ln();
+
+        BoneInfo ikBone = bone.getIKBone();
+        if(bone.getBoneType() == BoneType.LINKEDROT){
+            ind().put("<rotationRatio ");
+            putIntAttr("ratio", bone.getRotationRatio());
+            put(" />").ln();
+        }else if(ikBone != null){
+            ind().put("<ikBone ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, ikBone);
+            put(" /> ");
+            String ikBoneName = "Ref:" + ikBone.getBoneName().getText();
+            putLineComment(ikBoneName);
+            ln();
+        }
+
+        StringBuilder chainComment = new StringBuilder();
+        ind().put("<boneChain");
+        BoneInfo prev = bone.getPrevBone();
+        BoneInfo next = bone.getNextBone();
+        if(prev != null){
+            sp();
+            putNumberedIdAttr("prevBoneIdRef", PFX_BONE, prev);
+            chainComment.append('[')
+                        .append(prev.getBoneName().getPrimaryText())
+                        .append(']')
+                        .append("=> #");
+        }
+        if(next != null){
+            sp();
+            putNumberedIdAttr("nextBoneIdRef", PFX_BONE, next);
+            if(chainComment.length() <= 0) chainComment.append("#");
+            chainComment.append(" =>")
+                        .append('[')
+                        .append(next.getBoneName().getPrimaryText())
+                        .append(']');
+        }
+        put(" />").ln();
+        ind().putLineComment(chainComment).ln();
+
+        popNest();
+        ind().put("</bone>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * ボーングループリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBoneGroupList(PmdModel model)
+            throws IOException{
+        ind().put("<boneGroupList>").ln(2);
+        pushNest();
+
+        for(BoneGroup group : model.getBoneGroupList()){
+            if(group.isDefaultBoneGroup()) continue;
+            putBoneGroup(group);
+        }
+
+        popNest();
+        ind().put("</boneGroupList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のボーングループ情報を出力する。
+     * @param group ボーングループ情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putBoneGroup(BoneGroup group)
+            throws IOException{
+        I18nText i18nName = group.getGroupName();
+
+        putLocalNameComment(i18nName).ln();
+        ind().put("<boneGroup ");
+        putPrimaryNameAttr("name", i18nName);
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        for(BoneInfo bone : group){
+            ind().put("<boneGroupMember ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, bone);
+            put(" /> ");
+            String boneName = "Ref:" + bone.getBoneName().getText();
+            putLineComment(boneName).ln();
+        }
+
+        popNest();
+        ind().put("</boneGroup>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * IKチェーンリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putIKChainList(PmdModel model)
+            throws IOException{
+        ind().put("<ikChainList>").ln(2);
+        pushNest();
+
+        for(IKChain chain : model.getIKChainList()){
+            putIKChain(chain);
+        }
+
+        popNest();
+        ind().put("</ikChainList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のIKチェーン情報を出力する。
+     * @param chain チェーン情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putIKChain(IKChain chain)
+            throws IOException{
+        int depth = chain.getIKDepth();
+        float weight = chain.getIKWeight();
+        BoneInfo ikBone = chain.getIkBone();
+
+        ind().putLineComment("Ref:" + ikBone.getBoneName().getText()).ln();
+        ind().put("<ikChain ");
+        putNumberedIdAttr("ikBoneIdRef", PFX_BONE, ikBone).sp();
+        putIntAttr("recursiveDepth", depth).sp();
+        putFloatAttr("weight", weight);
+        put("> ").ln();
+        pushNest();
+
+        for(BoneInfo bone : chain){
+            ind().put("<chainOrder ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, bone);
+            put(" /> ");
+            putLineComment("Ref:" + bone.getBoneName().getText());
+            ln();
+        }
+
+        popNest();
+        ind().put("</ikChain>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * モーフリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMorphList(PmdModel model)
+            throws IOException{
+        ind().put("<morphList>").ln(2);
+        pushNest();
+
+        putBlockComment(MORPHTYPE_COMMENT).ln();
+
+        Map<MorphType, List<MorphPart>> morphMap = model.getMorphMap();
+        for(MorphType type : MorphType.values()){
+            if(type == MorphType.BASE) continue;
+            List<MorphPart> partList = morphMap.get(type);
+            if(partList == null) continue;
+            for(MorphPart part : partList){
+                putMorphPart(part);
+            }
+        }
+
+        popNest();
+        ind().put("</morphList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のモーフ情報を出力する。
+     * @param part モーフ情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putMorphPart(MorphPart part)
+            throws IOException{
+        I18nText i18nName = part.getMorphName();
+        String primary = i18nName.getPrimaryText();
+
+        ind().put("<morph ");
+        putAttr("name", primary).sp();
+        putAttr("type", part.getMorphType().name());
+        put(">");
+        putUnescapedComment(primary);
+        ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        for(MorphVertex mvertex : part){
+            MkPos3D offset = mvertex.getOffset();
+            Vertex base = mvertex.getBaseVertex();
+
+            ind().put("<morphVertex ");
+            putNumberedIdAttr("vtxIdRef", PFX_VERTEX, base).sp();
+            putFloatAttr("xOff", (float) offset.getXpos()).sp();
+            putFloatAttr("yOff", (float) offset.getYpos()).sp();
+            putFloatAttr("zOff", (float) offset.getZpos()).sp();
+            put("/>");
+            ln();
+        }
+
+        popNest();
+        ind().put("</morph>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 剛体リストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigidList(PmdModel model)
+            throws IOException{
+        ind().put("<rigidList>").ln(2);
+        pushNest();
+
+        putBlockComment(RIGIDBEHAVIOR_COMMENT).ln();
+
+        for(RigidInfo rigid : model.getRigidList()){
+            putRigid(rigid);
+        }
+
+        popNest();
+        ind().put("</rigidList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別の剛体情報を出力する。
+     * @param rigid 剛体情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigid(RigidInfo rigid)
+            throws IOException{
+        BoneInfo linkedBone = rigid.getLinkedBone();
+        I18nText i18nName = rigid.getRigidName();
+        String primary = i18nName.getPrimaryText();
+
+        putLocalNameComment(i18nName).ln();
+        ind().put("<rigid ");
+        putAttr("name", primary).sp();
+        putNumberedIdAttr("rigidId", PFX_RIGID, rigid).sp();
+        putAttr("behavior", rigid.getBehaviorType().name());
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        if(linkedBone != null){
+            ind().put("<linkedBone ");
+            putNumberedIdAttr("boneIdRef", PFX_BONE, linkedBone);
+            put(" /> ");
+            putLineComment("Ref:" + linkedBone.getBoneName().getText());
+            ln(2);
+        }
+
+        RigidShape shape = rigid.getRigidShape();
+        putRigidShape(shape);
+
+        MkPos3D position = rigid.getPosition();
+        ind().putPosition(position).ln();
+
+        Rad3d rotation = rigid.getRotation();
+        ind().putRadRotation(rotation).ln();
+
+        DynamicsInfo dynamics = rigid.getDynamicsInfo();
+        putDynamics(dynamics).ln();
+
+        for(RigidGroup group : rigid.getThroughGroupColl()){
+            ind().put("<throughRigidGroup ");
+            putNumberedIdAttr("rigidGroupIdRef",
+                              PFX_RIGIDGROUP,
+                              group.getSerialNumber() + 1).sp();
+            put(" />").ln();
+        }
+
+        popNest();
+        ind().put("</rigid>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 剛体形状を出力する。
+     * @param shape 剛体形状
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigidShape(RigidShape shape)
+            throws IOException{
+        RigidShapeType type = shape.getShapeType();
+
+        switch(type){
+        case BOX:
+            ind().put("<rigidShapeBox ");
+            putFloatAttr("width", shape.getWidth()).sp();
+            putFloatAttr("height", shape.getHeight()).sp();
+            putFloatAttr("depth", shape.getDepth()).sp();
+            break;
+        case SPHERE:
+            ind().put("<rigidShapeSphere ");
+            putFloatAttr("radius", shape.getRadius()).sp();
+            break;
+        case CAPSULE:
+            ind().put("<rigidShapeCapsule ");
+            putFloatAttr("height", shape.getHeight()).sp();
+            putFloatAttr("radius", shape.getRadius()).sp();
+            break;
+        default:
+            assert false;
+            throw new AssertionError();
+        }
+
+        put("/>").ln();
+
+        return this;
+    }
+
+    /**
+     * 力学設定を出力する。
+     * @param dynamics 力学設定
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putDynamics(DynamicsInfo dynamics)
+            throws IOException{
+        ind().put("<dynamics").ln();
+        pushNest();
+        ind().putFloatAttr("mass", dynamics.getMass()).ln();
+        ind().putFloatAttr("dampingPosition",
+                dynamics.getDampingPosition()).ln();
+        ind().putFloatAttr("dampingRotation",
+                dynamics.getDampingRotation()).ln();
+        ind().putFloatAttr("restitution", dynamics.getRestitution()).ln();
+        ind().putFloatAttr("friction", dynamics.getFriction()).ln();
+        popNest();
+        ind().put("/>").ln();
+
+        return this;
+    }
+
+    /**
+     * 剛体グループリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putRigidGroupList(PmdModel model)
+            throws IOException{
+        ind().put("<rigidGroupList>").ln(2);
+        pushNest();
+
+        for(RigidGroup group : model.getRigidGroupList()){
+            ind().put("<rigidGroup ");
+            putNumberedIdAttr("rigidGroupId",
+                              PFX_RIGIDGROUP,
+                              group.getSerialNumber() + 1);
+            List<RigidInfo> rigidList = group.getRigidList();
+            if(rigidList.size() <= 0){
+                put(" />").ln(2);
+                continue;
+            }
+            put(">").ln();
+            pushNest();
+
+            for(RigidInfo rigid : rigidList){
+                ind().put("<rigidGroupMember ");
+                putNumberedIdAttr("rigidIdRef", PFX_RIGID, rigid).sp();
+                put("/>");
+                sp();
+                putLineComment("Ref:" + rigid.getRigidName().getText());
+                ln();
+            }
+
+            popNest();
+            ind().put("</rigidGroup>").ln(2);
+        }
+
+        popNest();
+        ind().put("</rigidGroupList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * ジョイントリストを出力する。
+     * @param model モデルデータ
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putJointList(PmdModel model)
+            throws IOException{
+        ind().put("<jointList>").ln(2);
+        pushNest();
+
+        for(JointInfo joint : model.getJointList()){
+            putJoint(joint);
+        }
+
+        popNest();
+        ind().put("</jointList>").ln(2);
+
+        return this;
+    }
+
+    /**
+     * 個別のジョイント情報を出力する。
+     * @param joint ジョイント情報
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    private PmdXmlExporter putJoint(JointInfo joint)
+            throws IOException{
+        I18nText i18nName = joint.getJointName();
+
+        putLocalNameComment(i18nName).ln();
+        ind().put("<joint ");
+        putPrimaryNameAttr("name", i18nName);
+        put(">").ln();
+        pushNest();
+
+        putI18nName(i18nName);
+
+        RigidInfo rigidA = joint.getRigidA();
+        RigidInfo rigidB = joint.getRigidB();
+        ind().put("<jointedRigidPair ");
+        putNumberedIdAttr("rigidIdRef1", PFX_RIGID, rigidA).sp();
+        putNumberedIdAttr("rigidIdRef2", PFX_RIGID, rigidB).sp();
+        put("/>").ln();
+        ind();
+        putLineComment("[" + rigidA.getRigidName().getText() + "]"
+                + " <=> [" + rigidB.getRigidName().getText() + "]");
+        ln(2);
+
+        MkPos3D position = joint.getPosition();
+        ind().putPosition(position).ln();
+
+        TripletRange posRange = joint.getPositionRange();
+        ind().put("<limitPosition").ln();
+        pushNest();
+        ind();
+        putFloatAttr("xFrom", posRange.getXFrom()).sp();
+        putFloatAttr("xTo",   posRange.getXTo()).ln();
+        ind();
+        putFloatAttr("yFrom", posRange.getYFrom()).sp();
+        putFloatAttr("yTo",   posRange.getYTo()).ln();
+        ind();
+        putFloatAttr("zFrom", posRange.getZFrom()).sp();
+        putFloatAttr("zTo",   posRange.getZTo()).ln();
+        popNest();
+        ind().put("/>").ln(2);
+
+        Rad3d rotation = joint.getRotation();
+        ind().putRadRotation(rotation).ln();
+        TripletRange rotRange = joint.getRotationRange();
+        ind().put("<limitRotation").ln();
+        pushNest();
+        ind();
+        putFloatAttr("xFrom", rotRange.getXFrom()).sp();
+        putFloatAttr("xTo",   rotRange.getXTo()).ln();
+        ind();
+        putFloatAttr("yFrom", rotRange.getYFrom()).sp();
+        putFloatAttr("yTo",   rotRange.getYTo()).ln();
+        ind();
+        putFloatAttr("zFrom", rotRange.getZFrom()).sp();
+        putFloatAttr("zTo",   rotRange.getZTo()).ln();
+        popNest();
+        ind().put("/>").ln(2);
+
+        MkPos3D elaPosition = joint.getElasticPosition();
+        ind().put("<elasticPosition ");
+        putFloatAttr("x", (float) elaPosition.getXpos()).sp();
+        putFloatAttr("y", (float) elaPosition.getYpos()).sp();
+        putFloatAttr("z", (float) elaPosition.getZpos()).sp();
+        put("/>").ln();
+
+        Deg3d elaRotation = joint.getElasticRotation();
+        ind().put("<elasticRotation ");
+        putFloatAttr("xDeg", elaRotation.getXDeg()).sp();
+        putFloatAttr("yDeg", elaRotation.getYDeg()).sp();
+        putFloatAttr("zDeg", elaRotation.getZDeg()).sp();
+        put("/>").ln(2);
+
+        popNest();
+        ind().put("</joint>").ln(2);
+
+        return this;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/PmdXmlResources.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/PmdXmlResources.java
new file mode 100644 (file)
index 0000000..6c80df3
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * xml resources for PMD-XML
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.xml;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import jp.sourceforge.mikutoga.xml.XmlResourceResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * XML各種リソースの定義。
+ */
+public final class PmdXmlResources {
+
+    public static final String NS_PMDXML =
+            "http://mikutoga.sourceforge.jp/xml/ns/pmdxml/101009";
+    public static final String SCHEMA_PMDXML =
+            "http://mikutoga.sourceforge.jp/xml/xsd/pmdxml-101009.xsd";
+    public static final String DTD_PMDXML =
+            "http://mikutoga.sourceforge.jp/xml/dtd/pmdxml-101009.dtd";
+    public static final String VER_PMDXML =
+            "101009";
+    public static final String LOCAL_SCHEMA_PMDXML =
+            "resources/pmdxml-101009.xsd";
+    public static final String LOCAL_DTD_PMDXML =
+            "resources/pmdxml-101009.dtd";
+
+    public static final URI URI_SCHEMA_PMDXML = URI.create(SCHEMA_PMDXML);
+    public static final URI URI_DTD_PMDXML = URI.create(DTD_PMDXML);
+    public static final URI RES_SCHEMA_PMDXML;
+    public static final URI RES_DTD_PMDXML;
+
+    private static final Class THISCLASS = PmdXmlResources.class;
+
+    static{
+        Object dummy = new PmdXmlResources();
+
+        try{
+            RES_SCHEMA_PMDXML =
+                    THISCLASS.getResource(LOCAL_SCHEMA_PMDXML).toURI();
+            RES_DTD_PMDXML =
+                    THISCLASS.getResource(LOCAL_DTD_PMDXML).toURI();
+        }catch(URISyntaxException e){
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private PmdXmlResources(){
+        super();
+        assert this.getClass().equals(THISCLASS);
+        return;
+    }
+
+    /**
+     * ビルダの生成。
+     * @param handler エラーハンドラ
+     * @return ビルダ
+     * @throws SAXException パースエラー
+     * @throws ParserConfigurationException 構成エラー
+     */
+    public static DocumentBuilder newBuilder(ErrorHandler handler)
+            throws SAXException, ParserConfigurationException {
+        XmlResourceResolver resolver = new XmlResourceResolver();
+        resolver.putURIMap(URI_SCHEMA_PMDXML, RES_SCHEMA_PMDXML);
+        resolver.putURIMap(URI_DTD_PMDXML, RES_DTD_PMDXML);
+
+        SchemaFactory schemaFactory =
+                SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        schemaFactory.setResourceResolver(resolver);
+        schemaFactory.setErrorHandler(handler);
+        Schema schema = schemaFactory.newSchema();
+
+        DocumentBuilderFactory builderFactory =
+                DocumentBuilderFactory.newInstance();
+        builderFactory.setCoalescing(true);
+        builderFactory.setExpandEntityReferences(true);
+        builderFactory.setIgnoringComments(true);
+        builderFactory.setIgnoringElementContentWhitespace(false);
+        builderFactory.setNamespaceAware(true);
+        builderFactory.setValidating(false);
+        builderFactory.setSchema(schema);
+
+        DocumentBuilder builder = builderFactory.newDocumentBuilder();
+        builder.setEntityResolver(resolver);
+        builder.setErrorHandler(handler);
+
+        return builder;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/Xml2PmdLoader.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/Xml2PmdLoader.java
new file mode 100644 (file)
index 0000000..d5235b0
--- /dev/null
@@ -0,0 +1,977 @@
+/*
+ * xml loader
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.xml;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import jp.sourceforge.mikutoga.corelib.I18nText;
+import jp.sourceforge.mikutoga.pmd.model.ListUtil;
+import jp.sourceforge.mikutoga.math.MkPos2D;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkVec3D;
+import jp.sourceforge.mikutoga.pmd.BoneType;
+import jp.sourceforge.mikutoga.pmd.Deg3d;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+import jp.sourceforge.mikutoga.pmd.Rad3d;
+import jp.sourceforge.mikutoga.pmd.RigidBehaviorType;
+import jp.sourceforge.mikutoga.pmd.RigidShapeType;
+import jp.sourceforge.mikutoga.pmd.TripletRange;
+import jp.sourceforge.mikutoga.pmd.model.BoneGroup;
+import jp.sourceforge.mikutoga.pmd.model.BoneInfo;
+import jp.sourceforge.mikutoga.pmd.model.DynamicsInfo;
+import jp.sourceforge.mikutoga.pmd.model.IKChain;
+import jp.sourceforge.mikutoga.pmd.model.JointInfo;
+import jp.sourceforge.mikutoga.pmd.model.Material;
+import jp.sourceforge.mikutoga.pmd.model.MorphPart;
+import jp.sourceforge.mikutoga.pmd.model.MorphVertex;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.RigidGroup;
+import jp.sourceforge.mikutoga.pmd.model.RigidInfo;
+import jp.sourceforge.mikutoga.pmd.model.RigidShape;
+import jp.sourceforge.mikutoga.pmd.model.ShadeInfo;
+import jp.sourceforge.mikutoga.pmd.model.Surface;
+import jp.sourceforge.mikutoga.pmd.model.ToonMap;
+import jp.sourceforge.mikutoga.pmd.model.Vertex;
+import jp.sourceforge.mikutoga.xml.DomUtils;
+import jp.sourceforge.mikutoga.xml.TogaXmlException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * XML形式でのモデルファイルを読み込む。
+ */
+public class Xml2PmdLoader {
+
+    private final DocumentBuilder builder;
+
+    private PmdModel model;
+
+    private final Map<String, Integer> toonIdxMap =
+            new HashMap<String, Integer>();
+    private final Map<String, BoneInfo> boneMap =
+            new HashMap<String, BoneInfo>();
+    private final Map<String, Vertex> vertexMap =
+            new HashMap<String, Vertex>();
+    private final Map<String, List<Surface>> surfaceGroupMap =
+            new HashMap<String, List<Surface>>();
+    private final Map<String, RigidInfo> rigidMap =
+            new HashMap<String, RigidInfo>();
+    private final Map<String, RigidGroup> rigidGroupMap =
+            new HashMap<String, RigidGroup>();
+
+
+    /**
+     * コンストラクタ。
+     * @param builder ビルダ
+     */
+    public Xml2PmdLoader(DocumentBuilder builder){
+        super();
+        this.builder = builder;
+        return;
+    }
+
+    /**
+     * 要素からxsd:string型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return 文字列
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    private static String getStringAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomUtils.getStringAttr(elem, attrName);
+    }
+
+    /**
+     * 要素からxsd:boolean型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return 真ならtrue
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    private static boolean getBooleanAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomUtils.getBooleanAttr(elem, attrName);
+    }
+
+    /**
+     * 要素からxsd:integer型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return int値
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    private static int getIntegerAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomUtils.getIntegerAttr(elem, attrName);
+    }
+
+    /**
+     * 要素からxsd:float型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return float値
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    private static float getFloatAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomUtils.getFloatAttr(elem, attrName);
+    }
+
+    /**
+     * 要素から日本語Windows用ファイル名を属性値として読み取る。
+     * 念のため文字U+00A5は文字U-005Cに変換される。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return ファイル名
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    private static String getSjisFileNameAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomUtils.getSjisFileNameAttr(elem, attrName);
+    }
+
+    /**
+     * 指定された名前の子要素を1つだけ返す。
+     * @param parent 親要素
+     * @param tagName 子要素名
+     * @return 子要素
+     * @throws TogaXmlException 1つも見つからなかった
+     */
+    private static Element getChild(Element parent, String tagName)
+            throws TogaXmlException{
+        return DomUtils.getChild(parent, tagName);
+    }
+
+    /**
+     * 親要素が指定された名前の子要素を持つか判定する。
+     * @param parent 親要素
+     * @param tagName 子要素名
+     * @return 指定名の子要素が存在すればtrue
+     */
+    private static boolean hasChild(Element parent, String tagName){
+        return DomUtils.hasChild(parent, tagName);
+    }
+
+    /**
+     * 指定された名前の子要素のforeachを返す。
+     * @param parent 親要素
+     * @param childTag 子要素名
+     * @return 子要素のforeach
+     */
+    private static Iterable<Element> eachChild(Element parent,
+                                                    String childTag){
+        return DomUtils.getEachChild(parent, childTag);
+    }
+
+    /**
+     * グローバル名を取得する。
+     * 元要素のname属性及びi18nNameタグを持つ子要素が検索対象
+     * @param parent 元要素
+     * @return グローバル名。なければnull
+     */
+    private static String getGlobalName(Element parent){
+        NodeList nodeList = parent.getElementsByTagName("i18nName");
+        int length = nodeList.getLength();
+        for(int idx = 0; idx < length; idx++){
+            Node i18nNameNode = nodeList.item(idx);
+            Element i18nNameElem = (Element)i18nNameNode;
+            String lang = i18nNameElem.getAttribute("lang");
+            if(lang == null || lang.length() <= 0) continue;
+            if(lang.equals("en")){
+                String name = i18nNameElem.getAttribute("name");
+                return name;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * brタグで区切られた文字列内容(Mixed content)を改行付き文字列に変換する。
+     * brタグはその出現回数だけ\nに変換される。
+     * 生文字列コンテンツ中の\n,\rは削除される。
+     * 改行文字以外のホワイトスペースは保持される。
+     * @param parent br要素及び文字列コンテンツを含む要素
+     * @return 変換された文字列
+     */
+    private static String getBRedContent(Element parent){
+        StringBuilder result = new StringBuilder();
+
+        for(Node node = parent.getFirstChild();
+            node != null;
+            node = node.getNextSibling() ){
+
+            switch(node.getNodeType()){
+            case Node.ELEMENT_NODE:
+                Element elem = (Element) node;
+                if("br".equals(elem.getTagName())){
+                    result.append('\n');
+                }
+                break;
+            case Node.TEXT_NODE:
+            case Node.CDATA_SECTION_NODE:
+                String content = node.getTextContent();
+                content = content.replace("\r", "");
+                content = content.replace("\n", "");
+                result.append(content);
+                break;
+            default:
+                break;
+            }
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * 多言語名を取得する。
+     * @param baseElement 元要素
+     * @param text 多言語名
+     * @throws TogaXmlException あるべき属性が存在しない。
+     */
+    private static void buildI18nName(Element baseElement, I18nText text)
+            throws TogaXmlException{
+        String primaryText;
+        primaryText = getStringAttr(baseElement, "name");
+        text.setPrimaryText(primaryText);
+
+        for(Element i18nNameElem : eachChild(baseElement, "i18nName")){
+            String lang = getStringAttr(i18nNameElem, "lang");
+            String name = getStringAttr(i18nNameElem, "name");
+            if("en".equals(lang)){
+                text.setGlobalText(name);
+            }else{
+                text.setI18nText(lang, text);
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * XMLのパースを開始する。
+     * @param source XML入力
+     * @return モデルデータ
+     * @throws SAXException 構文エラー
+     * @throws IOException 入力エラー
+     * @throws TogaXmlException 構文エラー
+     */
+    public PmdModel parse(InputSource source)
+            throws SAXException, IOException, TogaXmlException{
+        Document document = this.builder.parse(source);
+
+        this.model = new PmdModel();
+
+        Element pmdModelElem = document.getDocumentElement();
+
+        buildBasicInfo(pmdModelElem);
+
+        buildBoneList(pmdModelElem);
+        buildVertexList(pmdModelElem);
+        buildSurfaceList(pmdModelElem);
+
+        buildToonMap(pmdModelElem);
+        buildMaterialList(pmdModelElem);
+        buildIkChainList(pmdModelElem);
+        buildMorphList(pmdModelElem);
+        buildBoneGroupList(pmdModelElem);
+
+        buildRigidList(pmdModelElem);
+        buildRigidGroupList(pmdModelElem);
+        resolveThroughRigidGroup(pmdModelElem);
+
+        buildJointList(pmdModelElem);
+
+        return this.model;
+    }
+
+    private void buildBasicInfo(Element pmdModelElem)
+            throws TogaXmlException{
+        String primaryName = getStringAttr(pmdModelElem, "name");
+        String globalName = getGlobalName(pmdModelElem);
+
+        I18nText modelName = this.model.getModelName();
+        modelName.setPrimaryText(primaryName);
+        modelName.setGlobalText(globalName);
+
+        String primaryDescription = null;
+        String globalDescription = null;
+        for(Element descriptionElem :
+            eachChild(pmdModelElem, "description")){
+            String descriptionText = getBRedContent(descriptionElem);
+            if( ! descriptionElem.hasAttribute("lang") ){
+                primaryDescription = descriptionText;
+            }else{
+                String lang = getStringAttr(descriptionElem, "lang");
+                if(lang.equals("ja")){
+                    primaryDescription = descriptionText;
+                }else if(lang.equals("en")){
+                    globalDescription = descriptionText;
+                }
+            }
+        }
+
+        I18nText description = this.model.getDescription();
+        description.setPrimaryText(primaryDescription);
+        description.setGlobalText(globalDescription);
+
+        return;
+    }
+
+    private void buildToonMap(Element pmdModelElem)
+            throws TogaXmlException{
+        ToonMap toonMap = this.model.getToonMap();
+
+        Element toonMapElem = getChild(pmdModelElem, "toonMap");
+
+        for(Element toonDefElem : eachChild(toonMapElem, "toonDef")){
+            String toonFileId = getStringAttr(toonDefElem, "toonFileId");
+            int toonIndex = getIntegerAttr(toonDefElem, "index");
+            String toonFile = getSjisFileNameAttr(toonDefElem, "winFileName");
+
+            toonMap.setIndexedToon(toonIndex, toonFile);
+            this.toonIdxMap.put(toonFileId, toonIndex);
+        }
+
+        return;
+    }
+
+    private void buildBoneList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element boneListElem = getChild(pmdModelElem, "boneList");
+
+        List<BoneInfo> boneList = this.model.getBoneList();
+
+        for(Element boneElem : eachChild(boneListElem, "bone")){
+            BoneInfo boneInfo = new BoneInfo();
+            boneList.add(boneInfo);
+
+            I18nText boneName = boneInfo.getBoneName();
+            buildI18nName(boneElem, boneName);
+
+            String boneType = getStringAttr(boneElem, "type");
+            BoneType type = BoneType.valueOf(boneType);
+            boneInfo.setBoneType(type);
+
+            String boneId = getStringAttr(boneElem, "boneId");
+            this.boneMap.put(boneId, boneInfo);
+
+            Element positionElem = getChild(boneElem, "position");
+            float xPos = getFloatAttr(positionElem, "x");
+            float yPos = getFloatAttr(positionElem, "y");
+            float zPos = getFloatAttr(positionElem, "z");
+            MkPos3D position = boneInfo.getPosition();
+            position.setXpos(xPos);
+            position.setYpos(yPos);
+            position.setZpos(zPos);
+        }
+
+        ListUtil.assignIndexedSerial(boneList);
+
+        int serial = 0;
+        for(Element boneElem : eachChild(boneListElem, "bone")){
+            BoneInfo boneInfo = boneList.get(serial++);
+
+            if(hasChild(boneElem, "ikBone")){
+                Element ikBoneElem = getChild(boneElem, "ikBone");
+                String ikBoneId = getStringAttr(ikBoneElem, "boneIdRef");
+                BoneInfo ikBone = this.boneMap.get(ikBoneId);
+                boneInfo.setIKBone(ikBone);
+            }else if(hasChild(boneElem, "rotationRatio")){
+                Element ikBoneElem = getChild(boneElem, "rotationRatio");
+                int ratio = getIntegerAttr(ikBoneElem, "ratio");
+                boneInfo.setRotationRatio(ratio);
+            }
+
+            Element boneChainElem = getChild(boneElem, "boneChain");
+            if(boneChainElem.hasAttribute("prevBoneIdRef")){
+                String prevId = getStringAttr(boneChainElem, "prevBoneIdRef");
+                BoneInfo prevBone = this.boneMap.get(prevId);
+                boneInfo.setPrevBone(prevBone);
+            }
+            if(boneChainElem.hasAttribute("nextBoneIdRef")){
+                String nextId = getStringAttr(boneChainElem, "nextBoneIdRef");
+                BoneInfo nextBone = this.boneMap.get(nextId);
+                boneInfo.setNextBone(nextBone);
+            }
+        }
+
+        return;
+    }
+
+    private void buildVertexList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element vertexListElem = getChild(pmdModelElem, "vertexList");
+
+        List<Vertex> vertexList = this.model.getVertexList();
+
+        for(Element vertexElem : eachChild(vertexListElem, "vertex")){
+            Vertex vertex = new Vertex();
+            vertexList.add(vertex);
+
+            String vertexId = getStringAttr(vertexElem, "vtxId");
+            this.vertexMap.put(vertexId, vertex);
+
+            boolean showEdge = getBooleanAttr(vertexElem, "showEdge");
+            vertex.setEdgeAppearance(showEdge);
+
+            float xVal;
+            float yVal;
+            float zVal;
+
+            Element positionElem = getChild(vertexElem, "position");
+            xVal = getFloatAttr(positionElem, "x");
+            yVal = getFloatAttr(positionElem, "y");
+            zVal = getFloatAttr(positionElem, "z");
+            MkPos3D position = vertex.getPosition();
+            position.setXpos(xVal);
+            position.setYpos(yVal);
+            position.setZpos(zVal);
+
+            Element normalElem = getChild(vertexElem, "normal");
+            xVal = getFloatAttr(normalElem, "x");
+            yVal = getFloatAttr(normalElem, "y");
+            zVal = getFloatAttr(normalElem, "z");
+            MkVec3D normal = vertex.getNormal();
+            normal.setXVal(xVal);
+            normal.setYVal(yVal);
+            normal.setZVal(zVal);
+
+            Element uvElem = getChild(vertexElem, "uvMap");
+            float uVal = getFloatAttr(uvElem, "u");
+            float vVal = getFloatAttr(uvElem, "v");
+            MkPos2D uv = vertex.getUVPosition();
+            uv.setXpos(uVal);
+            uv.setYpos(vVal);
+
+            Element skinningElem = getChild(vertexElem, "skinning");
+            String boneId1 = getStringAttr(skinningElem, "boneIdRef1");
+            String boneId2 = getStringAttr(skinningElem, "boneIdRef2");
+            int weight = getIntegerAttr(skinningElem, "weightBalance");
+            BoneInfo boneA = this.boneMap.get(boneId1);
+            BoneInfo boneB = this.boneMap.get(boneId2);
+            vertex.setBonePair(boneA, boneB);
+            vertex.setWeightA(weight);
+        }
+
+        ListUtil.assignIndexedSerial(vertexList);
+
+        return;
+    }
+
+    private void buildSurfaceList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element surfaceGroupListElem =
+                getChild(pmdModelElem, "surfaceGroupList");
+
+        for(Element surfaceGroupElem :
+            eachChild(surfaceGroupListElem, "surfaceGroup") ){
+
+            String groupId =
+                    getStringAttr(surfaceGroupElem, "surfaceGroupId");
+            List<Surface> surfaceList = buildSurface(surfaceGroupElem);
+
+            this.surfaceGroupMap.put(groupId, surfaceList);
+        }
+    }
+
+    private List<Surface> buildSurface(Element surfaceGroupElem)
+            throws TogaXmlException{
+        List<Surface> result = new ArrayList<Surface>();
+
+        for(Element surfaceElem : eachChild(surfaceGroupElem, "surface")){
+            Surface surface = new Surface();
+            result.add(surface);
+
+            String id1 = getStringAttr(surfaceElem, "vtxIdRef1");
+            String id2 = getStringAttr(surfaceElem, "vtxIdRef2");
+            String id3 = getStringAttr(surfaceElem, "vtxIdRef3");
+
+            Vertex vertex1 = this.vertexMap.get(id1);
+            Vertex vertex2 = this.vertexMap.get(id2);
+            Vertex vertex3 = this.vertexMap.get(id3);
+
+            surface.setTriangle(vertex1, vertex2, vertex3);
+        }
+
+        return result;
+    }
+
+    private void buildMaterialList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element materialListElem =
+                getChild(pmdModelElem, "materialList");
+
+        List<Surface> surfaceList = this.model.getSurfaceList();
+        List<Material> materialList = this.model.getMaterialList();
+
+        for(Element materialElem : eachChild(materialListElem, "material")){
+            Material material = new Material();
+            materialList.add(material);
+
+            material.getShadeInfo().setToonMap(this.model.getToonMap());
+
+            String surfaceGroupId =
+                    getStringAttr(materialElem, "surfaceGroupIdRef");
+            List<Surface> surfaceGroup =
+                    this.surfaceGroupMap.get(surfaceGroupId);
+            surfaceList.addAll(surfaceGroup);
+            material.getSurfaceList().addAll(surfaceGroup);
+
+            boolean hasEdge = getBooleanAttr(materialElem, "showEdge");
+            material.setEdgeAppearance(hasEdge);
+
+            ShadeInfo shadeInfo = material.getShadeInfo();
+
+            int toonIdx;
+            if(hasChild(materialElem, "toon")){
+                Element toonElem = getChild(materialElem, "toon");
+                String toonId = getStringAttr(toonElem, "toonFileIdRef");
+                toonIdx = this.toonIdxMap.get(toonId);
+            }else{
+                toonIdx = 255;
+            }
+            shadeInfo.setToonIndex(toonIdx);
+
+            if(hasChild(materialElem, "textureFile")){
+                Element textureFileElem =
+                        getChild(materialElem, "textureFile");
+                String textureFile =
+                        getSjisFileNameAttr(textureFileElem, "winFileName");
+                shadeInfo.setTextureFileName(textureFile);
+            }
+
+            if(hasChild(materialElem, "spheremapFile")){
+                Element spheremapFileElem =
+                        getChild(materialElem, "spheremapFile");
+                String spheremapFile =
+                        getSjisFileNameAttr(spheremapFileElem, "winFileName");
+                shadeInfo.setSpheremapFileName(spheremapFile);
+            }
+
+            float red;
+            float green;
+            float blue;
+
+            Element diffuseElem = getChild(materialElem, "diffuse");
+            red   = getFloatAttr(diffuseElem, "r");
+            green = getFloatAttr(diffuseElem, "g");
+            blue  = getFloatAttr(diffuseElem, "b");
+            float alpha = getFloatAttr(diffuseElem, "alpha");
+            Color diffuse = new Color(red, green, blue, alpha);
+            material.setDiffuseColor(diffuse);
+
+            Element specularElem = getChild(materialElem, "specular");
+            red   = getFloatAttr(specularElem, "r");
+            green = getFloatAttr(specularElem, "g");
+            blue  = getFloatAttr(specularElem, "b");
+            float shininess = getFloatAttr(specularElem, "shininess");
+            Color specular = new Color(red, green, blue);
+            material.setSpecularColor(specular);
+            material.setShininess(shininess);
+
+            Element ambientElem = getChild(materialElem, "ambient");
+            red   = getFloatAttr(ambientElem, "r");
+            green = getFloatAttr(ambientElem, "g");
+            blue  = getFloatAttr(ambientElem, "b");
+            Color ambient = new Color(red, green, blue);
+            material.setAmbientColor(ambient);
+        }
+
+        return;
+    }
+
+    private void buildIkChainList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element ikChainListElem =
+                getChild(pmdModelElem, "ikChainList");
+
+        List<IKChain> ikChainList = this.model.getIKChainList();
+
+        for(Element ikChainElem : eachChild(ikChainListElem, "ikChain")){
+            IKChain ikChain = new IKChain();
+            ikChainList.add(ikChain);
+
+            String ikBoneIdRef = getStringAttr(ikChainElem, "ikBoneIdRef");
+            int rucursiveDepth =
+                    getIntegerAttr(ikChainElem, "recursiveDepth");
+            float weight = getFloatAttr(ikChainElem, "weight");
+
+            BoneInfo ikBone = this.boneMap.get(ikBoneIdRef);
+            ikChain.setIkBone(ikBone);
+            ikChain.setIKDepth(rucursiveDepth);
+            ikChain.setIKWeight(weight);
+
+            List<BoneInfo> chainList = ikChain.getChainedBoneList();
+
+            for(Element orderElem : eachChild(ikChainElem, "chainOrder")){
+                String boneIdRef = getStringAttr(orderElem, "boneIdRef");
+                BoneInfo chaindBone = this.boneMap.get(boneIdRef);
+                chainList.add(chaindBone);
+            }
+        }
+
+        return;
+    }
+
+    private void buildMorphList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element morphListElem =
+                getChild(pmdModelElem, "morphList");
+
+        Map<MorphType, List<MorphPart>> morphMap = this.model.getMorphMap();
+
+        for(Element morphElem : eachChild(morphListElem, "morph")){
+            MorphPart morphPart = new MorphPart();
+
+            I18nText name = morphPart.getMorphName();
+            buildI18nName(morphElem, name);
+
+            String type = getStringAttr(morphElem, "type");
+            MorphType morphType = MorphType.valueOf(type);
+            morphPart.setMorphType(morphType);
+
+            List<MorphVertex> morphVertexList =
+                    morphPart.getMorphVertexList();
+
+            for(Element morphVertexElem
+                    : eachChild(morphElem, "morphVertex")){
+                String vtxIdRef = getStringAttr(morphVertexElem, "vtxIdRef");
+                Vertex baseVertex = this.vertexMap.get(vtxIdRef);
+                float xOff = getFloatAttr(morphVertexElem, "xOff");
+                float yOff = getFloatAttr(morphVertexElem, "yOff");
+                float zOff = getFloatAttr(morphVertexElem, "zOff");
+
+                MorphVertex morphVertex = new MorphVertex();
+                morphVertex.setBaseVertex(baseVertex);
+                MkPos3D position = morphVertex.getOffset();
+                position.setXpos(xOff);
+                position.setYpos(yOff);
+                position.setZpos(zOff);
+
+                morphVertexList.add(morphVertex);
+            }
+
+            morphMap.get(morphType).add(morphPart);
+        }
+
+        List<MorphPart> serialList = new LinkedList<MorphPart>();
+        MorphPart baseDummy = new MorphPart();
+        serialList.add(baseDummy);
+        for(MorphPart part : morphMap.get(MorphType.EYEBROW)){
+            serialList.add(part);
+        }
+        for(MorphPart part : morphMap.get(MorphType.EYE)){
+            serialList.add(part);
+        }
+        for(MorphPart part : morphMap.get(MorphType.LIP)){
+            serialList.add(part);
+        }
+        for(MorphPart part : morphMap.get(MorphType.EXTRA)){
+            serialList.add(part);
+        }
+        ListUtil.assignIndexedSerial(serialList);
+
+        return;
+    }
+
+    private void buildBoneGroupList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element boneGroupListElem =
+                getChild(pmdModelElem, "boneGroupList");
+
+        List<BoneGroup> boneGroupList = this.model.getBoneGroupList();
+        BoneGroup defaultGroup = new BoneGroup();
+        boneGroupList.add(defaultGroup);
+
+        for(Element boneGroupElem
+                : eachChild(boneGroupListElem, "boneGroup")){
+            BoneGroup group = new BoneGroup();
+            boneGroupList.add(group);
+
+            I18nText name = group.getGroupName();
+            buildI18nName(boneGroupElem, name);
+
+            for(Element boneGroupMemberElem
+                    : eachChild(boneGroupElem, "boneGroupMember")){
+                String boneIdRef =
+                        getStringAttr(boneGroupMemberElem, "boneIdRef");
+                BoneInfo bone = this.boneMap.get(boneIdRef);
+                group.getBoneList().add(bone);
+            }
+        }
+
+        ListUtil.assignIndexedSerial(boneGroupList);
+
+        return;
+    }
+
+    private void buildRigidList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element rigidListElem =
+                getChild(pmdModelElem, "rigidList");
+
+        List<RigidInfo> rigidList = this.model.getRigidList();
+
+        for(Element rigidElem : eachChild(rigidListElem, "rigid")){
+            RigidInfo rigid = new RigidInfo();
+            rigidList.add(rigid);
+
+            I18nText name = rigid.getRigidName();
+            buildI18nName(rigidElem, name);
+
+            String behavior = getStringAttr(rigidElem, "behavior");
+            RigidBehaviorType type = RigidBehaviorType.valueOf(behavior);
+            rigid.setBehaviorType(type);
+
+            String rigidId = getStringAttr(rigidElem, "rigidId");
+            this.rigidMap.put(rigidId, rigid);
+
+            if(hasChild(rigidElem, "linkedBone")){
+                Element linkedBoneElem = getChild(rigidElem, "linkedBone");
+                String boneIdRef = getStringAttr(linkedBoneElem, "boneIdRef");
+                BoneInfo linkedBone = this.boneMap.get(boneIdRef);
+                rigid.setLinkedBone(linkedBone);
+            }
+
+            RigidShape rigidShape = rigid.getRigidShape();
+            if(hasChild(rigidElem, "rigidShapeSphere")){
+                Element shapeElem =
+                        getChild(rigidElem, "rigidShapeSphere");
+                float radius = getFloatAttr(shapeElem, "radius");
+                rigidShape.setShapeType(RigidShapeType.SPHERE);
+                rigidShape.setRadius(radius);
+            }
+            if(hasChild(rigidElem, "rigidShapeBox")){
+                Element shapeElem =
+                        getChild(rigidElem, "rigidShapeBox");
+                float width  = getFloatAttr(shapeElem, "width");
+                float height = getFloatAttr(shapeElem, "height");
+                float depth  = getFloatAttr(shapeElem, "depth");
+                rigidShape.setShapeType(RigidShapeType.BOX);
+                rigidShape.setWidth(width);
+                rigidShape.setHeight(height);
+                rigidShape.setDepth(depth);
+            }
+            if(hasChild(rigidElem, "rigidShapeCapsule")){
+                Element shapeElem =
+                        getChild(rigidElem, "rigidShapeCapsule");
+                float height = getFloatAttr(shapeElem, "height");
+                float radius = getFloatAttr(shapeElem, "radius");
+                rigidShape.setShapeType(RigidShapeType.CAPSULE);
+                rigidShape.setHeight(height);
+                rigidShape.setRadius(radius);
+            }
+
+            float xVal;
+            float yVal;
+            float zVal;
+
+            Element positionElem = getChild(rigidElem, "position");
+            xVal = getFloatAttr(positionElem, "x");
+            yVal = getFloatAttr(positionElem, "y");
+            zVal = getFloatAttr(positionElem, "z");
+            MkPos3D position = rigid.getPosition();
+            position.setXpos(xVal);
+            position.setYpos(yVal);
+            position.setZpos(zVal);
+
+            Element radRotationElem = getChild(rigidElem, "radRotation");
+            xVal = getFloatAttr(radRotationElem, "xRad");
+            yVal = getFloatAttr(radRotationElem, "yRad");
+            zVal = getFloatAttr(radRotationElem, "zRad");
+            Rad3d rotation = rigid.getRotation();
+            rotation.setXRad(xVal);
+            rotation.setYRad(yVal);
+            rotation.setZRad(zVal);
+
+            Element dynamicsElem = getChild(rigidElem, "dynamics");
+            float mass = getFloatAttr(dynamicsElem, "mass");
+            float dampingPosition =
+                    getFloatAttr(dynamicsElem, "dampingPosition");
+            float dampingRotation =
+                    getFloatAttr(dynamicsElem, "dampingRotation");
+            float restitution = getFloatAttr(dynamicsElem, "restitution");
+            float friction = getFloatAttr(dynamicsElem, "friction");
+            DynamicsInfo dynamics = rigid.getDynamicsInfo();
+            dynamics.setMass(mass);
+            dynamics.setDampingPosition(dampingPosition);
+            dynamics.setDampingRotation(dampingRotation);
+            dynamics.setRestitution(restitution);
+            dynamics.setFriction(friction);
+        }
+
+        ListUtil.assignIndexedSerial(rigidList);
+
+        return;
+    }
+
+    private void buildRigidGroupList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element rigidGroupListElem =
+                getChild(pmdModelElem, "rigidGroupList");
+
+        List<RigidGroup> groupList = this.model.getRigidGroupList();
+
+        for(Element rigidGroupElem
+                : eachChild(rigidGroupListElem, "rigidGroup")){
+            RigidGroup rigidGroup = new RigidGroup();
+            groupList.add(rigidGroup);
+
+            String rigidGroupId =
+                    getStringAttr(rigidGroupElem, "rigidGroupId");
+            this.rigidGroupMap.put(rigidGroupId, rigidGroup);
+
+            for(Element memberElem
+                    : eachChild(rigidGroupElem, "rigidGroupMember")){
+                String rigidIdRef = getStringAttr(memberElem, "rigidIdRef");
+                RigidInfo rigid = this.rigidMap.get(rigidIdRef);
+                rigidGroup.getRigidList().add(rigid);
+                rigid.setRigidGroup(rigidGroup);
+            }
+        }
+
+        while(groupList.size() < 16){
+            RigidGroup rigidGroup = new RigidGroup();
+            groupList.add(rigidGroup);
+        }
+
+        ListUtil.assignIndexedSerial(groupList);
+
+        return;
+    }
+
+    private void resolveThroughRigidGroup(Element pmdModelElem)
+            throws TogaXmlException{
+        Element rigidListElem =
+                getChild(pmdModelElem, "rigidList");
+
+        List<RigidInfo> rigidList = this.model.getRigidList();
+
+        int serialNum = 0;
+        for(Element rigidElem : eachChild(rigidListElem, "rigid")){
+            RigidInfo rigid = rigidList.get(serialNum++);
+            for(Element groupElem
+                    : eachChild(rigidElem, "throughRigidGroup")){
+                String groupId = getStringAttr(groupElem, "rigidGroupIdRef");
+                RigidGroup group = this.rigidGroupMap.get(groupId);
+                rigid.getThroughGroupColl().add(group);
+            }
+        }
+
+        return;
+    }
+
+    private void buildJointList(Element pmdModelElem)
+            throws TogaXmlException{
+        Element jointListElem =
+                getChild(pmdModelElem, "jointList");
+
+        List<JointInfo> jointList = this.model.getJointList();
+
+        for(Element jointElem : eachChild(jointListElem, "joint")){
+            JointInfo joint = new JointInfo();
+            jointList.add(joint);
+
+            I18nText name = joint.getJointName();
+            buildI18nName(jointElem, name);
+
+            Element rigidPairElem = getChild(jointElem, "jointedRigidPair");
+            String rigidIdRef1 = getStringAttr(rigidPairElem, "rigidIdRef1");
+            String rigidIdRef2 = getStringAttr(rigidPairElem, "rigidIdRef2");
+            RigidInfo rigid1 = this.rigidMap.get(rigidIdRef1);
+            RigidInfo rigid2 = this.rigidMap.get(rigidIdRef2);
+            joint.setRigidPair(rigid1, rigid2);
+
+            float xVal;
+            float yVal;
+            float zVal;
+            float xFrom;
+            float xTo;
+            float yFrom;
+            float yTo;
+            float zFrom;
+            float zTo;
+
+            MkPos3D position = joint.getPosition();
+            Element positionElem = getChild(jointElem, "position");
+            xVal = getFloatAttr(positionElem, "x");
+            yVal = getFloatAttr(positionElem, "y");
+            zVal = getFloatAttr(positionElem, "z");
+            position.setXpos(xVal);
+            position.setYpos(yVal);
+            position.setZpos(zVal);
+
+            TripletRange limitPosition = joint.getPositionRange();
+            Element limitPositionElem = getChild(jointElem, "limitPosition");
+            xFrom = getFloatAttr(limitPositionElem, "xFrom");
+            xTo   = getFloatAttr(limitPositionElem, "xTo");
+            yFrom = getFloatAttr(limitPositionElem, "yFrom");
+            yTo   = getFloatAttr(limitPositionElem, "yTo");
+            zFrom = getFloatAttr(limitPositionElem, "zFrom");
+            zTo   = getFloatAttr(limitPositionElem, "zTo");
+            limitPosition.setXRange(xFrom, xTo);
+            limitPosition.setYRange(yFrom, yTo);
+            limitPosition.setZRange(zFrom, zTo);
+
+            Rad3d rotation = joint.getRotation();
+            Element rotationElem = getChild(jointElem, "radRotation");
+            xVal = getFloatAttr(rotationElem, "xRad");
+            yVal = getFloatAttr(rotationElem, "yRad");
+            zVal = getFloatAttr(rotationElem, "zRad");
+            rotation.setXRad(xVal);
+            rotation.setYRad(yVal);
+            rotation.setZRad(zVal);
+
+            TripletRange limitRotation = joint.getRotationRange();
+            Element limitRotationElem = getChild(jointElem, "limitRotation");
+            xFrom = getFloatAttr(limitRotationElem, "xFrom");
+            xTo   = getFloatAttr(limitRotationElem, "xTo");
+            yFrom = getFloatAttr(limitRotationElem, "yFrom");
+            yTo   = getFloatAttr(limitRotationElem, "yTo");
+            zFrom = getFloatAttr(limitRotationElem, "zFrom");
+            zTo   = getFloatAttr(limitRotationElem, "zTo");
+            limitRotation.setXRange(xFrom, xTo);
+            limitRotation.setYRange(yFrom, yTo);
+            limitRotation.setZRange(zFrom, zTo);
+
+            MkPos3D elasticPosition = joint.getElasticPosition();
+            Element elasticPositionElem =
+                    getChild(jointElem, "elasticPosition");
+            xVal = getFloatAttr(elasticPositionElem, "x");
+            yVal = getFloatAttr(elasticPositionElem, "y");
+            zVal = getFloatAttr(elasticPositionElem, "z");
+            elasticPosition.setXpos(xVal);
+            elasticPosition.setYpos(yVal);
+            elasticPosition.setZpos(zVal);
+
+            Deg3d elasticRotation = joint.getElasticRotation();
+            Element elasticRotationElem =
+                    getChild(jointElem, "elasticRotation");
+            xVal = getFloatAttr(elasticRotationElem, "xDeg");
+            yVal = getFloatAttr(elasticRotationElem, "yDeg");
+            zVal = getFloatAttr(elasticRotationElem, "zDeg");
+            elasticRotation.setXDeg(xVal);
+            elasticRotation.setYDeg(yVal);
+            elasticRotation.setZDeg(zVal);
+        }
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/package-info.java b/src/main/java/jp/sourceforge/mikutoga/pmd/model/xml/package-info.java
new file mode 100644 (file)
index 0000000..4ae0dda
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+/**
+ * PMDモデル内容をXMLで出力するためのライブラリ。
+ */
+
+package jp.sourceforge.mikutoga.pmd.model.xml;
+
+/* EOF */
index 400a8e9..20d1192 100644 (file)
@@ -21,13 +21,13 @@ import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.ParserConfigurationException;
 import jp.sourceforge.mikutoga.parser.MmdFormatException;
 import jp.sourceforge.mikutoga.parser.MmdSource;
 import javax.xml.parsers.ParserConfigurationException;
 import jp.sourceforge.mikutoga.parser.MmdFormatException;
 import jp.sourceforge.mikutoga.parser.MmdSource;
-import jp.sourceforge.mikutoga.pmd.PmdModel;
-import jp.sourceforge.mikutoga.pmd.pmdexporter.IllegalPmdException;
-import jp.sourceforge.mikutoga.pmd.pmdexporter.PmdExporter;
-import jp.sourceforge.mikutoga.pmd.pmdloader.PmdLoader;
-import jp.sourceforge.mikutoga.pmd.xml.PmdXmlExporter;
-import jp.sourceforge.mikutoga.pmd.xml.PmdXmlResources;
-import jp.sourceforge.mikutoga.pmd.xml.Xml2PmdLoader;
+import jp.sourceforge.mikutoga.pmd.IllegalPmdDataException;
+import jp.sourceforge.mikutoga.pmd.model.PmdModel;
+import jp.sourceforge.mikutoga.pmd.model.binio.PmdExporter;
+import jp.sourceforge.mikutoga.pmd.model.binio.PmdLoader;
+import jp.sourceforge.mikutoga.pmd.model.xml.PmdXmlExporter;
+import jp.sourceforge.mikutoga.pmd.model.xml.PmdXmlResources;
+import jp.sourceforge.mikutoga.pmd.model.xml.Xml2PmdLoader;
 import jp.sourceforge.mikutoga.xml.TogaXmlException;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 import jp.sourceforge.mikutoga.xml.TogaXmlException;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -37,7 +37,7 @@ import org.xml.sax.SAXException;
  */
 public final class Pmd2Xml {
 
  */
 public final class Pmd2Xml {
 
-    private static final Class THISCLASS;
+    private static final Class<?> THISCLASS;
     private static final String APPNAME;
     private static final String APPVER;
     private static final String APPLICENSE;
     private static final String APPNAME;
     private static final String APPVER;
     private static final String APPLICENSE;
@@ -179,7 +179,7 @@ public final class Pmd2Xml {
             ioError(e);
         }catch(ParserConfigurationException e){
             internalError(e);
             ioError(e);
         }catch(ParserConfigurationException e){
             internalError(e);
-        }catch(IllegalPmdException e){
+        }catch(IllegalPmdDataException e){
             internalError(e);
         }catch(MmdFormatException e){
             pmdError(e);
             internalError(e);
         }catch(MmdFormatException e){
             pmdError(e);
@@ -273,10 +273,10 @@ public final class Pmd2Xml {
      * @param outputFile 出力ファイル名
      * @throws IOException 入出力エラー
      * @throws MmdFormatException 不正なPMDファイル
      * @param outputFile 出力ファイル名
      * @throws IOException 入出力エラー
      * @throws MmdFormatException 不正なPMDファイル
-     * @throws IllegalPmdException 不正なモデルデータ
+     * @throws IllegalPmdDataException 不正なモデルデータ
      */
     private static void pmd2xml(String inputFile, String outputFile)
      */
     private static void pmd2xml(String inputFile, String outputFile)
-            throws IOException, MmdFormatException, IllegalPmdException{
+            throws IOException, MmdFormatException, IllegalPmdDataException{
         File iFile = new File(inputFile);
         InputStream is = new FileInputStream(iFile);
         is = new BufferedInputStream(is);
         File iFile = new File(inputFile);
         InputStream is = new FileInputStream(iFile);
         is = new BufferedInputStream(is);
@@ -302,14 +302,14 @@ public final class Pmd2Xml {
      * @throws ParserConfigurationException XML構成のエラー
      * @throws SAXException 不正なXMLファイル
      * @throws TogaXmlException 不正なXMLファイル
      * @throws ParserConfigurationException XML構成のエラー
      * @throws SAXException 不正なXMLファイル
      * @throws TogaXmlException 不正なXMLファイル
-     * @throws IllegalPmdException 不正なPMDモデルデータ
+     * @throws IllegalPmdDataException 不正なPMDモデルデータ
      */
     private static void xml2pmd(String inputFile, String outputFile)
             throws IOException,
                    ParserConfigurationException,
                    SAXException,
                    TogaXmlException,
      */
     private static void xml2pmd(String inputFile, String outputFile)
             throws IOException,
                    ParserConfigurationException,
                    SAXException,
                    TogaXmlException,
-                   IllegalPmdException {
+                   IllegalPmdDataException {
         File iFile = new File(inputFile);
         InputStream is = new FileInputStream(iFile);
         is = new BufferedInputStream(is);
         File iFile = new File(inputFile);
         InputStream is = new FileInputStream(iFile);
         is = new BufferedInputStream(is);
@@ -392,10 +392,10 @@ public final class Pmd2Xml {
      * @param model モデルデータ
      * @param ostream 出力ストリーム
      * @throws IOException 出力エラー
      * @param model モデルデータ
      * @param ostream 出力ストリーム
      * @throws IOException 出力エラー
-     * @throws IllegalPmdException 不正なモデルデータ
+     * @throws IllegalPmdDataException 不正なモデルデータ
      */
     private static void pmdOut(PmdModel model, OutputStream ostream)
      */
     private static void pmdOut(PmdModel model, OutputStream ostream)
-            throws IOException, IllegalPmdException{
+            throws IOException, IllegalPmdDataException{
         PmdExporter exporter = new PmdExporter(ostream);
         exporter.dumpPmdModel(model);
         ostream.close();
         PmdExporter exporter = new PmdExporter(ostream);
         exporter.dumpPmdModel(model);
         ostream.close();
@@ -407,10 +407,10 @@ public final class Pmd2Xml {
      * @param model モデルデータ
      * @param ostream 出力ストリーム
      * @throws IOException 出力エラー
      * @param model モデルデータ
      * @param ostream 出力ストリーム
      * @throws IOException 出力エラー
-     * @throws IllegalPmdException 不正なモデルデータ
+     * @throws IllegalPmdDataException 不正なモデルデータ
      */
     private static void xmlOut(PmdModel model, OutputStream ostream)
      */
     private static void xmlOut(PmdModel model, OutputStream ostream)
-            throws IOException, IllegalPmdException{
+            throws IOException, IllegalPmdDataException{
         PmdXmlExporter exporter = new PmdXmlExporter(ostream);
         exporter.setNewLine("\r\n");
         exporter.setGenerator(APPNAME + ' ' + APPVER);
         PmdXmlExporter exporter = new PmdXmlExporter(ostream);
         exporter.setNewLine("\r\n");
         exporter.setGenerator(APPNAME + ' ' + APPVER);
diff --git a/src/main/resources/jp/sourceforge/mikutoga/pmd/model/xml/resources/pmdxml-101009.dtd b/src/main/resources/jp/sourceforge/mikutoga/pmd/model/xml/resources/pmdxml-101009.dtd
new file mode 100644 (file)
index 0000000..851f3aa
--- /dev/null
@@ -0,0 +1,532 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+  MikuMikuDance
+    model-data(*.pmd) on XML
+    DTD definition
+
+  License : The MIT License
+  Copyright(c) 2010 MikuToga Partners
+
+  SYSTEM ID: http://mikutoga.sourceforge.jp/xml/dtd/pmdxml-101009.dtd
+-->
+
+
+<!-- ROOT -->
+<!ELEMENT pmdModel (
+    i18nName*, description+, license*, credits*, meta*,
+    materialList,
+    toonMap,
+    boneList,
+    boneGroupList,
+    ikChainList,
+    morphList,
+    rigidList,
+    rigidGroupList,
+    jointList,
+    surfaceGroupList,
+    vertexList
+) >
+<!ATTLIST pmdModel
+    xmlns              CDATA #IMPLIED
+    xmlns:xsi          CDATA #IMPLIED
+    xsi:schemaLocation CDATA #IMPLIED
+    schemaVersion      CDATA #FIXED "100923"
+    name               CDATA #REQUIRED
+>
+
+
+<!-- Multilingual name. -->
+<!ELEMENT i18nName EMPTY>
+<!ATTLIST i18nName
+    lang CDATA #REQUIRED
+    name CDATA #REQUIRED
+>
+
+
+<!-- Break line. -->
+<!ELEMENT br EMPTY>
+
+
+<!-- Description note about model. -->
+<!ELEMENT description (#PCDATA | br)* >
+<!ATTLIST description
+    lang      CDATA "ja"
+    xml:space CDATA #FIXED "preserve"
+>
+
+
+<!-- License term of usage. -->
+<!ELEMENT license (#PCDATA | br)* >
+<!ATTLIST license
+    lang      CDATA "ja"
+    xml:space CDATA #FIXED "preserve"
+>
+
+
+<!-- Credits for someone. -->
+<!ELEMENT credits (#PCDATA | br)* >
+<!ATTLIST credits
+    lang      CDATA "ja"
+    xml:space CDATA #FIXED "preserve"
+>
+
+
+<!--
+Meta-information of model.
+Use free.
+but, some meta-name has recommended usage.
++ "generator" (Generator application name)
++ "siteURL" (Website URL)
++ "imageURL" (Thumbnail image URL)
+-->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+    name    CDATA #REQUIRED
+    content CDATA #REQUIRED
+>
+
+
+<!--
+Material list.
+All visual things can be tracked from here.
+-->
+<!ELEMENT materialList (material)* >
+
+
+<!--
+Material definition.
+Colors, shading, any other definitions.
+-->
+<!ELEMENT material (
+    i18nName*,
+    diffuse,
+    specular,
+    ambient,
+    toon?,
+    textureFile?,
+    spheremapFile?
+)*>
+<!ATTLIST material
+    name              CDATA #IMPLIED
+    showEdge          CDATA #REQUIRED
+    surfaceGroupIdRef CDATA #REQUIRED
+>
+
+
+<!-- Diffuse color definition. -->
+<!ELEMENT diffuse EMPTY>
+<!ATTLIST diffuse
+    r     CDATA #REQUIRED
+    g     CDATA #REQUIRED
+    b     CDATA #REQUIRED
+    alpha CDATA #REQUIRED
+>
+
+
+<!-- Specular color definition. -->
+<!ELEMENT specular EMPTY>
+<!ATTLIST specular
+    r         CDATA #REQUIRED
+    g         CDATA #REQUIRED
+    b         CDATA #REQUIRED
+    shininess CDATA #REQUIRED
+>
+
+
+<!-- Ambient color definition. -->
+<!ELEMENT ambient EMPTY>
+<!ATTLIST ambient
+    r CDATA #REQUIRED
+    g CDATA #REQUIRED
+    b CDATA #REQUIRED
+>
+
+
+<!-- Reference for Toon-image. -->
+<!ELEMENT toon EMPTY>
+<!ATTLIST toon
+    toonFileIdRef CDATA #REQUIRED
+>
+
+
+<!-- Texture file information. -->
+<!ELEMENT textureFile EMPTY>
+<!ATTLIST textureFile
+    winFileName CDATA #REQUIRED
+>
+
+
+<!-- Sphere-map file information. -->
+<!ELEMENT spheremapFile EMPTY>
+<!ATTLIST spheremapFile
+    winFileName CDATA #REQUIRED
+>
+
+
+<!-- Toon-file mappings. -->
+<!ELEMENT toonMap (toonDef)* >
+
+
+<!-- Toon-file information. -->
+<!ELEMENT toonDef EMPTY>
+<!ATTLIST toonDef
+    toonFileId  CDATA #REQUIRED
+    index       CDATA #REQUIRED
+    winFileName CDATA #REQUIRED
+>
+
+
+<!-- List of bone information. -->
+<!ELEMENT boneList (bone)* >
+
+
+<!-- Bone definition. -->
+<!ELEMENT bone (
+    i18nName*,
+    position,
+    (rotationRatio | ikBone)?,
+    boneChain
+) >
+<!ATTLIST bone
+    name   CDATA #REQUIRED
+    boneId CDATA #REQUIRED
+    type   (
+          ROTATE
+        | ROTMOV
+        | IK
+        | UNKNOWN
+        | UNDERIK
+        | UNDERROT
+        | IKCONNECTED
+        | HIDDEN
+        | TWIST
+        | LINKEDROT
+    ) #REQUIRED
+>
+
+
+<!-- position information. -->
+<!ELEMENT position EMPTY>
+<!ATTLIST position
+    x CDATA #REQUIRED
+    y CDATA #REQUIRED
+    z CDATA #REQUIRED
+>
+
+
+<!-- Relationship-info between bones. -->
+<!ELEMENT boneChain EMPTY>
+<!ATTLIST boneChain
+    prevBoneIdRef CDATA #IMPLIED
+    nextBoneIdRef CDATA #IMPLIED
+>
+
+
+<!-- Rotation ratio between Linked-rotationed bones. -->
+<!ELEMENT rotationRatio EMPTY>
+<!ATTLIST rotationRatio
+    ratio CDATA #REQUIRED
+>
+
+
+<!-- Reference to IK-Bone. -->
+<!ELEMENT ikBone EMPTY>
+<!ATTLIST ikBone
+    boneIdRef CDATA #REQUIRED
+>
+
+
+<!-- List of Bone-group. -->
+<!ELEMENT boneGroupList (boneGroup)* >
+
+
+<!-- Bone-group. -->
+<!ELEMENT boneGroup (i18nName*, boneGroupMember*) >
+<!ATTLIST boneGroup
+    name CDATA #REQUIRED
+>
+
+
+<!-- Member of Bone-group. -->
+<!ELEMENT boneGroupMember EMPTY>
+<!ATTLIST boneGroupMember
+    boneIdRef CDATA #REQUIRED
+>
+
+
+<!-- List of IK chain. -->
+<!ELEMENT ikChainList (ikChain)* >
+
+
+<!-- IK chained bones definition. -->
+<!ELEMENT ikChain (chainOrder)+ >
+<!ATTLIST ikChain
+    ikBoneIdRef    CDATA #REQUIRED
+    recursiveDepth CDATA #REQUIRED
+    weight         CDATA #REQUIRED
+>
+
+
+<!-- Part of IK chained bones. -->
+<!ELEMENT chainOrder EMPTY>
+<!ATTLIST chainOrder
+    boneIdRef CDATA #REQUIRED
+>
+
+
+<!-- List of morphing definition. -->
+<!ELEMENT morphList (morph)* >
+
+
+<!-- Morphing definition. -->
+<!ELEMENT morph (i18nName*, morphVertex*) >
+<!ATTLIST morph
+    name CDATA #REQUIRED
+    type (
+          EYEBROW
+        | EYE
+        | LIP
+        | EXTRA
+    ) #REQUIRED
+>
+
+
+<!-- Morphing vertex information. -->
+<!ELEMENT morphVertex EMPTY>
+<!ATTLIST morphVertex
+    vtxIdRef CDATA #REQUIRED
+    xOff     CDATA #REQUIRED
+    yOff     CDATA #REQUIRED
+    zOff     CDATA #REQUIRED
+>
+
+
+<!-- list of Rigid-body definition. -->
+<!ELEMENT rigidList (rigid)* >
+
+
+<!-- Rigid-body definition. -->
+<!ELEMENT rigid (
+    i18nName*,
+    linkedBone?,
+    (rigidShapeSphere | rigidShapeBox | rigidShapeCapsule),
+    position,
+    radRotation,
+    dynamics,
+    throughRigidGroup*
+) >
+<!ATTLIST rigid
+    name     CDATA #REQUIRED
+    rigidId  CDATA #REQUIRED
+    behavior (
+          FOLLOWBONE
+        | ONLYDYNAMICS
+        | BONEDDYNAMICS
+    ) #REQUIRED
+>
+
+
+<!-- Referenced Bone from Rigid-body. -->
+<!ELEMENT linkedBone EMPTY>
+<!ATTLIST linkedBone
+    boneIdRef CDATA #REQUIRED
+>
+
+
+<!-- Sphere shape of Rigid-body. -->
+<!ELEMENT rigidShapeSphere EMPTY>
+<!ATTLIST rigidShapeSphere
+    radius CDATA #REQUIRED
+>
+
+
+<!-- Box shape of Rigid-body. -->
+<!ELEMENT rigidShapeBox EMPTY>
+<!ATTLIST rigidShapeBox
+    width  CDATA #REQUIRED
+    height CDATA #REQUIRED
+    depth  CDATA #REQUIRED
+>
+
+
+<!-- Capsule shape of Rigid-body. -->
+<!ELEMENT rigidShapeCapsule EMPTY>
+<!ATTLIST rigidShapeCapsule
+    height CDATA #REQUIRED
+    radius CDATA #REQUIRED
+>
+
+
+<!-- Rotaion information by radian. -->
+<!ELEMENT radRotation EMPTY>
+<!ATTLIST radRotation
+    xRad CDATA #REQUIRED
+    yRad CDATA #REQUIRED
+    zRad CDATA #REQUIRED
+>
+
+
+<!-- Dynamics parameters. -->
+<!ELEMENT dynamics EMPTY>
+<!ATTLIST dynamics
+    mass            CDATA #REQUIRED
+    dampingPosition CDATA #REQUIRED
+    dampingRotation CDATA #REQUIRED
+    restitution     CDATA #REQUIRED
+    friction        CDATA #REQUIRED
+>
+
+
+<!-- Reference for non-collision rigid-group. -->
+<!ELEMENT throughRigidGroup EMPTY>
+<!ATTLIST throughRigidGroup
+    rigidGroupIdRef CDATA #REQUIRED
+>
+
+
+<!-- List of Rigid-body group. -->
+<!ELEMENT rigidGroupList (rigidGroup)* >
+
+
+<!-- Rigid-body group. -->
+<!ELEMENT rigidGroup (rigidGroupMember)* >
+<!ATTLIST rigidGroup
+    rigidGroupId CDATA #REQUIRED
+>
+
+
+<!-- Member of Rigid-body group. -->
+<!ELEMENT rigidGroupMember EMPTY>
+<!ATTLIST rigidGroupMember
+    rigidIdRef CDATA #REQUIRED
+>
+
+
+<!-- List of joint definition. -->
+<!ELEMENT jointList (joint)* >
+
+
+<!-- Joint definition. -->
+<!ELEMENT joint (
+    i18nName*,
+    jointedRigidPair,
+    position,    limitPosition,
+    radRotation, limitRotation,
+    elasticPosition,
+    elasticRotation
+) >
+<!ATTLIST joint
+    name CDATA #REQUIRED
+>
+
+
+<!-- Jointed-bones information. -->
+<!ELEMENT jointedRigidPair EMPTY>
+<!ATTLIST jointedRigidPair
+    rigidIdRef2 CDATA #REQUIRED
+    rigidIdRef1 CDATA #REQUIRED
+>
+
+
+<!-- Limit of Position. -->
+<!ELEMENT limitPosition EMPTY>
+<!ATTLIST limitPosition
+    xFrom CDATA #REQUIRED
+    xTo   CDATA #REQUIRED
+    yFrom CDATA #REQUIRED
+    yTo   CDATA #REQUIRED
+    zFrom CDATA #REQUIRED
+    zTo   CDATA #REQUIRED
+>
+
+
+<!-- Limit of Rotation. -->
+<!ELEMENT limitRotation EMPTY>
+<!ATTLIST limitRotation
+    xFrom CDATA #REQUIRED
+    xTo   CDATA #REQUIRED
+    yFrom CDATA #REQUIRED
+    yTo   CDATA #REQUIRED
+    zFrom CDATA #REQUIRED
+    zTo   CDATA #REQUIRED
+>
+
+
+<!-- Elastic position of joint. -->
+<!ELEMENT elasticPosition EMPTY>
+<!ATTLIST elasticPosition
+    x CDATA #REQUIRED
+    y CDATA #REQUIRED
+    z CDATA #REQUIRED
+>
+
+
+<!-- Elastic rotation of joint by degree. -->
+<!ELEMENT elasticRotation EMPTY>
+<!ATTLIST elasticRotation
+    xDeg CDATA #REQUIRED
+    yDeg CDATA #REQUIRED
+    zDeg CDATA #REQUIRED
+>
+
+
+<!-- List of surface group. -->
+<!ELEMENT surfaceGroupList (surfaceGroup)* >
+
+
+<!-- Surface group. -->
+<!ELEMENT surfaceGroup (surface)* >
+<!ATTLIST surfaceGroup
+    surfaceGroupId CDATA #REQUIRED
+>
+
+
+<!-- Each surface with Triangle. -->
+<!ELEMENT surface EMPTY>
+<!ATTLIST surface
+    vtxIdRef1 CDATA #REQUIRED
+    vtxIdRef2 CDATA #REQUIRED
+    vtxIdRef3 CDATA #REQUIRED
+>
+
+
+<!-- List of vertex. -->
+<!ELEMENT vertexList (vertex)* >
+
+
+<!-- Vertex definition. -->
+<!ELEMENT vertex (position, normal, uvMap, skinning) >
+<!ATTLIST vertex
+    vtxId    CDATA #REQUIRED
+    showEdge CDATA #REQUIRED
+>
+
+
+<!-- Normal vector definition. -->
+<!ELEMENT normal EMPTY>
+<!ATTLIST normal
+    x CDATA #REQUIRED
+    y CDATA #REQUIRED
+    z CDATA #REQUIRED
+>
+
+
+<!-- UV-mapping information. -->
+<!ELEMENT uvMap EMPTY>
+<!ATTLIST uvMap
+    u CDATA #REQUIRED
+    v CDATA #REQUIRED
+>
+
+
+<!-- Skinning definition from vertex to bone. -->
+<!ELEMENT skinning EMPTY>
+<!ATTLIST skinning
+    boneIdRef1    CDATA #REQUIRED
+    boneIdRef2    CDATA #REQUIRED
+    weightBalance CDATA #REQUIRED
+>
+
+
+<!-- EOF -->
diff --git a/src/main/resources/jp/sourceforge/mikutoga/pmd/model/xml/resources/pmdxml-101009.xsd b/src/main/resources/jp/sourceforge/mikutoga/pmd/model/xml/resources/pmdxml-101009.xsd
new file mode 100644 (file)
index 0000000..424b7bb
--- /dev/null
@@ -0,0 +1,1511 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+  MikuMikuDance
+    model-data(*.pmd) on XML
+    schema definition
+
+  License : The MIT License
+  Copyright(c) 2010 MikuToga Partners
+-->
+
+
+<xsd:schema
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+
+    targetNamespace="http://mikutoga.sourceforge.jp/xml/ns/pmdxml/101009"
+    xmlns:tns      ="http://mikutoga.sourceforge.jp/xml/ns/pmdxml/101009"
+
+    elementFormDefault="qualified"
+    version="101009"
+>
+
+    <xsd:annotation>
+        <xsd:documentation>
+            MikuMikuDance model-data(*.pmd) on XML.
+            License : The MIT License
+            Copyright(c) 2010 MikuToga Partners
+        </xsd:documentation>
+    </xsd:annotation>
+
+
+    <xsd:import
+        namespace="http://www.w3.org/XML/1998/namespace"
+        schemaLocation="http://www.w3.org/2001/xml.xsd" />
+
+
+    <!-- ROOT -->
+    <xsd:element name="pmdModel">
+        <xsd:annotation>
+            <xsd:documentation>
+                Root element.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:i18nName"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:description"
+                    minOccurs="1" maxOccurs="unbounded" />
+                <xsd:element ref="tns:license"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:credits"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:meta"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:materialList" />
+                <xsd:element ref="tns:toonMap" />
+                <xsd:element ref="tns:boneList" />
+                <xsd:element ref="tns:boneGroupList" />
+                <xsd:element ref="tns:ikChainList" />
+                <xsd:element ref="tns:morphList" />
+                <xsd:element ref="tns:rigidList" />
+                <xsd:element ref="tns:rigidGroupList" />
+                <xsd:element ref="tns:jointList" />
+                <xsd:element ref="tns:surfaceGroupList" />
+                <xsd:element ref="tns:vertexList" />
+            </xsd:sequence>
+            <xsd:attribute name="name" type="xsd:string" use="required" />
+            <xsd:attribute
+                name="schemaVersion"
+                type="xsd:string"
+                use="required"
+                fixed="101009" />
+        </xsd:complexType>
+
+        <xsd:unique name="ModelLang">
+            <xsd:selector xpath="./tns:i18nName" />
+            <xsd:field xpath="@lang" />
+        </xsd:unique>
+
+        <xsd:unique name="DescriptionLang">
+            <xsd:selector xpath="./tns:description" />
+            <xsd:field xpath="@lang" />
+        </xsd:unique>
+
+        <xsd:unique name="LicenseLang">
+            <xsd:selector xpath="./tns:license" />
+            <xsd:field xpath="@lang" />
+        </xsd:unique>
+
+        <xsd:unique name="CreditsLang">
+            <xsd:selector xpath="./tns:credits" />
+            <xsd:field xpath="@lang" />
+        </xsd:unique>
+
+        <xsd:keyref name="Material-SurfaceGroup" refer="tns:SurfaceGroup-Id">
+            <xsd:selector xpath="./tns:materialList/tns:material" />
+            <xsd:field xpath="@surfaceGroupIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Material-Toon" refer="tns:Toon-Id">
+            <xsd:selector xpath="./tns:materialList/tns:material/tns:toon" />
+            <xsd:field xpath="@toonFileIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Bone-prev" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:boneList/tns:bone/tns:boneChain" />
+            <xsd:field xpath="@prevBoneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Bone-next" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:boneList/tns:bone/tns:boneChain" />
+            <xsd:field xpath="@nextBoneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Bone-ik" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:boneList/tns:bone/tns:ikBone" />
+            <xsd:field xpath="@boneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="BoneGroup-Bone" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:boneGroupList/tns:boneGroup/tns:boneGroupMember" />
+            <xsd:field xpath="@boneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="IkChain-Bone" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:ikChainList/tns:ikChain" />
+            <xsd:field xpath="@ikBoneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="IkChainPart-Bone" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:ikChainList/tns:ikChain/tns:chainOrder" />
+            <xsd:field xpath="@boneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Morph-Vertex" refer="tns:Vertex-Id">
+            <xsd:selector xpath="./tns:morphList/tns:morph/tns:morphVertex" />
+            <xsd:field xpath="@vtxIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Rigid-Bone" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:rigidList/tns:rigid/tns:linkedBone" />
+            <xsd:field xpath="@boneIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Rigid-RigidGroup" refer="tns:RigidGroup-Id">
+            <xsd:selector xpath="./tns:rigidList/tns:rigid/tns:throughRigidGroup" />
+            <xsd:field xpath="@rigidGroupIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="RigidGroup-Rigid" refer="tns:Rigid-Id">
+            <xsd:selector xpath="./tns:rigidGroupList/tns:rigidGroup/tns:rigidGroupMember" />
+            <xsd:field xpath="@rigidIdRef" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Joint-Rigid1" refer="tns:Rigid-Id">
+            <xsd:selector xpath="./tns:jointList/tns:joint/tns:jointedRigidPair" />
+            <xsd:field xpath="@rigidIdRef1" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Joint-Rigid2" refer="tns:Rigid-Id">
+            <xsd:selector xpath="./tns:jointList/tns:joint/tns:jointedRigidPair" />
+            <xsd:field xpath="@rigidIdRef2" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Surface-Vertex1" refer="tns:Vertex-Id">
+            <xsd:selector xpath="./tns:surfaceGroupList/tns:surfaceGroup/tns:surface" />
+            <xsd:field xpath="@vtxIdRef1" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Surface-Vertex2" refer="tns:Vertex-Id">
+            <xsd:selector xpath="./tns:surfaceGroupList/tns:surfaceGroup/tns:surface" />
+            <xsd:field xpath="@vtxIdRef2" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Surface-Vertex3" refer="tns:Vertex-Id">
+            <xsd:selector xpath="./tns:surfaceGroupList/tns:surfaceGroup/tns:surface" />
+            <xsd:field xpath="@vtxIdRef3" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Vertex-Bone1" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:vertexList/tns:vertex/tns:skinning" />
+            <xsd:field xpath="@boneIdRef1" />
+        </xsd:keyref>
+
+        <xsd:keyref name="Vertex-Bone2" refer="tns:Bone-Id">
+            <xsd:selector xpath="./tns:vertexList/tns:vertex/tns:skinning" />
+            <xsd:field xpath="@boneIdRef2" />
+        </xsd:keyref>
+
+    </xsd:element>
+
+
+    <xsd:simpleType name="NonJaLanguage">
+        <xsd:annotation>
+            <xsd:documentation>
+                xsd:language except "ja" (Primary-language in MMD)
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:restriction base="xsd:language">
+            <xsd:pattern value="([^jJ].*)|([jJ][^aA].*)|([jJ][aA].+)" />
+        </xsd:restriction>
+
+    </xsd:simpleType>
+
+
+    <xsd:element name="i18nName">
+        <xsd:annotation>
+            <xsd:documentation>
+                Multilingual name.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="lang"
+                type="tns:NonJaLanguage"
+                use="required" />
+            <xsd:attribute
+                name="name"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="br">
+        <xsd:annotation>
+            <xsd:documentation>
+                Break line.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+
+    <xsd:complexType name="bredContent" mixed="true">
+        <xsd:annotation>
+            <xsd:documentation>
+                Free paragraph with break-line.
+                Any raw-newline(CR,CRLF) will be ignored later.
+                But, other white-spaces will be preserved.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:sequence>
+            <xsd:element ref="tns:br" minOccurs="0" maxOccurs="unbounded" />
+        </xsd:sequence>
+
+        <xsd:attribute
+            name="lang"
+            type="xsd:language"
+            use="optional"
+            default="ja" />
+        <xsd:attribute ref="xml:space" use="optional" fixed="preserve" />
+
+    </xsd:complexType>
+
+
+    <xsd:element name="description" type="tns:bredContent">
+        <xsd:annotation>
+            <xsd:documentation>
+                Description note about model.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+
+    <xsd:element name="license" type="tns:bredContent">
+        <xsd:annotation>
+            <xsd:documentation>
+                License term of usage.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+
+    <xsd:element name="credits" type="tns:bredContent">
+        <xsd:annotation>
+            <xsd:documentation>
+                Credits for someone.
+            </xsd:documentation>
+        </xsd:annotation>
+    </xsd:element>
+
+
+    <xsd:element name="meta">
+        <xsd:annotation>
+            <xsd:documentation>
+                Meta-information of model.
+                Use free.
+                but, some meta-name has recommended usage.
+                + "generator" (Generator application name)
+                + "siteURL" (Website URL)
+                + "imageURL" (Thumbnail image URL)
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="name"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="content"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="materialList">
+        <xsd:annotation>
+            <xsd:documentation>
+                Material list.
+                All visual things can be tracked from here.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:material"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:unique name="SurfaceGroupRef">
+            <xsd:selector xpath="./tns:material" />
+            <xsd:field xpath="@surfaceGroupIdRef" />
+        </xsd:unique>
+
+    </xsd:element>
+
+
+    <xsd:element name="material">
+        <xsd:annotation>
+            <xsd:documentation>
+                Material definition.
+                Colors, shading, any other definitions.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:i18nName" minOccurs="0" maxOccurs="unbounded"/>
+                <xsd:element ref="tns:diffuse" />
+                <xsd:element ref="tns:specular" />
+                <xsd:element ref="tns:ambient" />
+                <xsd:element ref="tns:toon" minOccurs="0" />
+                <xsd:element ref="tns:textureFile" minOccurs="0" />
+                <xsd:element ref="tns:spheremapFile" minOccurs="0" />
+            </xsd:sequence>
+
+            <xsd:attribute
+                name="name"
+                type="xsd:string"
+                use="optional" />
+            <xsd:attribute
+                name="showEdge"
+                type="xsd:boolean"
+                use="required" />
+            <xsd:attribute
+                name="surfaceGroupIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:simpleType name="SrgbCompo">
+        <xsd:annotation>
+            <xsd:documentation>
+                sRGB component value. (0.0 - 1.0)
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:restriction base="xsd:float">
+            <xsd:minInclusive value="0.0" />
+            <xsd:maxInclusive value="1.0" />
+        </xsd:restriction>
+
+    </xsd:simpleType>
+
+
+    <xsd:element name="diffuse">
+        <xsd:annotation>
+            <xsd:documentation>
+                Diffuse color definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="r" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="g" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="b" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="alpha" type="tns:SrgbCompo" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="specular">
+        <xsd:annotation>
+            <xsd:documentation>
+                Specular color definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="r" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="g" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="b" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="shininess" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="ambient">
+        <xsd:annotation>
+            <xsd:documentation>
+                Ambient color definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="r" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="g" type="tns:SrgbCompo" use="required" />
+            <xsd:attribute name="b" type="tns:SrgbCompo" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="toon">
+        <xsd:annotation>
+            <xsd:documentation>
+                Reference for Toon-image.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="toonFileIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="textureFile">
+        <xsd:annotation>
+            <xsd:documentation>
+                Texture file information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="winFileName"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="spheremapFile">
+        <xsd:annotation>
+            <xsd:documentation>
+                Sphere-map file information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="winFileName"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="toonMap">
+        <xsd:annotation>
+            <xsd:documentation>
+                Toon-file mappings.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:toonDef"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:unique name="ToonIndex">
+            <xsd:selector xpath="./tns:toonDef" />
+            <xsd:field xpath="@index" />
+        </xsd:unique>
+
+        <xsd:key name="Toon-Id">
+            <xsd:selector xpath="./tns:toonDef" />
+            <xsd:field xpath="@toonFileId" />
+        </xsd:key>
+
+    </xsd:element>
+
+
+    <xsd:element name="toonDef">
+        <xsd:annotation>
+            <xsd:documentation>
+                Toon-file information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="toonFileId"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="index"
+                type="xsd:nonNegativeInteger"
+                use="required" />
+            <xsd:attribute
+                name="winFileName"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="boneList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of bone information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:bone" minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:key name="Bone-Id">
+            <xsd:selector xpath="./tns:bone" />
+            <xsd:field xpath="@boneId" />
+        </xsd:key>
+<!--
+        <xsd:unique name="BoneName">
+            <xsd:selector xpath="./tns:bone" />
+            <xsd:field xpath="@name" />
+        </xsd:unique>
+-->
+    </xsd:element>
+
+
+    <xsd:simpleType name="BoneType">
+        <xsd:annotation>
+            <xsd:documentation>
+                bone types.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="ROTATE" />      <!-- 回転 -->
+            <xsd:enumeration value="ROTMOV" />      <!-- 回転/移動 -->
+            <xsd:enumeration value="IK" />          <!-- IK -->
+            <xsd:enumeration value="UNKNOWN" />     <!-- 不明 -->
+            <xsd:enumeration value="UNDERIK" />     <!-- IK影響下(回転) -->
+            <xsd:enumeration value="UNDERROT" />    <!-- 回転影響下 -->
+            <xsd:enumeration value="IKCONNECTED" /> <!-- IK接続先 -->
+            <xsd:enumeration value="HIDDEN" />      <!-- 非表示 -->
+            <xsd:enumeration value="TWIST" />       <!-- 捩り -->
+            <xsd:enumeration value="LINKEDROT" />   <!-- 回転連動 -->
+        </xsd:restriction>
+
+    </xsd:simpleType>
+
+
+    <xsd:element name="bone">
+        <xsd:annotation>
+            <xsd:documentation>
+                Bone definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:i18nName" minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:position" />
+                <xsd:choice minOccurs="0">
+                    <xsd:element ref="tns:rotationRatio" />
+                    <xsd:element ref="tns:ikBone" />
+                </xsd:choice>
+                <xsd:element ref="tns:boneChain" />
+            </xsd:sequence>
+
+            <xsd:attribute name="name" type="xsd:string" use="required" />
+            <xsd:attribute name="boneId" type="xsd:NCName" use="required" />
+            <xsd:attribute name="type" type="tns:BoneType" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="boneChain">
+        <xsd:annotation>
+            <xsd:documentation>
+                Relationship-info between bones.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="prevBoneIdRef"
+                type="xsd:NCName"
+                use="optional" />
+            <xsd:attribute
+                name="nextBoneIdRef"
+                type="xsd:NCName"
+                use="optional" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="rotationRatio">
+        <xsd:annotation>
+            <xsd:documentation>
+                Rotation ratio between Linked-rotationed bones.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="ratio"
+                type="xsd:integer"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="ikBone">
+        <xsd:annotation>
+            <xsd:documentation>
+                Reference to IK-Bone.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="boneIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="boneGroupList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of Bone-group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:boneGroup"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+<!--
+        <xsd:unique name="BoneGroupName">
+            <xsd:selector xpath="./tns:boneGroup" />
+            <xsd:field xpath="@name" />
+        </xsd:unique>
+-->
+    </xsd:element>
+
+
+    <xsd:element name="boneGroup">
+        <xsd:annotation>
+            <xsd:documentation>
+                Bone-group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:i18nName"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element
+                    ref="tns:boneGroupMember"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+            <xsd:attribute
+                name="name"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="boneGroupMember">
+        <xsd:annotation>
+            <xsd:documentation>
+                Member of Bone-group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="boneIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="ikChainList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of IK chain.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:ikChain"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:unique name="IkChainBone">
+            <xsd:selector xpath="./tns:ikChain" />
+            <xsd:field xpath="@ikBoneIdRef" />
+        </xsd:unique>
+
+    </xsd:element>
+
+
+    <xsd:element name="ikChain">
+        <xsd:annotation>
+            <xsd:documentation>
+                IK chained bones definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:chainOrder"
+                    minOccurs="1" maxOccurs="unbounded" />
+            </xsd:sequence>
+
+            <xsd:attribute
+                name="ikBoneIdRef"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="recursiveDepth"
+                type="xsd:integer"
+                use="required" />
+            <xsd:attribute
+                name="weight"
+                type="xsd:float"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="chainOrder">
+        <xsd:annotation>
+            <xsd:documentation>
+                Part of IK chained bones.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="boneIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="morphList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of morphing definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:morph"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+<!--
+        <xsd:unique name="MorphName">
+            <xsd:selector xpath="./tns:morph" />
+            <xsd:field xpath="@name" />
+        </xsd:unique>
+-->
+    </xsd:element>
+
+
+    <xsd:simpleType name="MorphType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Morph types.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="EYEBROW" /> <!-- まゆ -->
+            <xsd:enumeration value="EYE" />     <!-- 目 -->
+            <xsd:enumeration value="LIP" />     <!-- リップ -->
+            <xsd:enumeration value="EXTRA" />   <!-- その他 -->
+        </xsd:restriction>
+
+    </xsd:simpleType>
+
+
+    <xsd:element name="morph">
+        <xsd:annotation>
+            <xsd:documentation>
+                Morphing definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:i18nName"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element
+                    ref="tns:morphVertex"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+            <xsd:attribute
+                name="name"
+                type="xsd:string"
+                use="required" />
+            <xsd:attribute name="type" type="tns:MorphType" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="morphVertex">
+        <xsd:annotation>
+            <xsd:documentation>
+                Morphing vertex information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="vtxIdRef" type="xsd:NCName" use="required" />
+            <xsd:attribute name="xOff" type="xsd:float" use="required" />
+            <xsd:attribute name="yOff" type="xsd:float" use="required" />
+            <xsd:attribute name="zOff" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidList">
+        <xsd:annotation>
+            <xsd:documentation>
+                list of Rigid-body definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:rigid"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:key name="Rigid-Id">
+            <xsd:selector xpath="./tns:rigid" />
+            <xsd:field xpath="@rigidId" />
+        </xsd:key>
+<!--
+        <xsd:unique name="RigidName">
+            <xsd:selector xpath="./tns:rigid" />
+            <xsd:field xpath="@name" />
+        </xsd:unique>
+-->
+    </xsd:element>
+
+
+    <xsd:simpleType name="RigidBehaviorType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Rigid bahavior types.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:restriction base="xsd:string">
+            <xsd:enumeration value="FOLLOWBONE" />    <!-- ボーン追従 -->
+            <xsd:enumeration value="ONLYDYNAMICS" />  <!-- 物理 -->
+            <xsd:enumeration value="BONEDDYNAMICS" /> <!-- 物理+ボーン -->
+        </xsd:restriction>
+
+    </xsd:simpleType>
+
+
+    <xsd:element name="rigid">
+        <xsd:annotation>
+            <xsd:documentation>
+                Rigid-body definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:i18nName"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:linkedBone" minOccurs="0" />
+
+                <xsd:choice>
+                    <xsd:element ref="tns:rigidShapeSphere" />
+                    <xsd:element ref="tns:rigidShapeBox" />
+                    <xsd:element ref="tns:rigidShapeCapsule" />
+                </xsd:choice>
+
+                <xsd:element ref="tns:position" />
+                <xsd:element ref="tns:radRotation" />
+                <xsd:element ref="tns:dynamics" />
+                <xsd:element
+                    ref="tns:throughRigidGroup"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+
+            <xsd:attribute name="name" type="xsd:string" use="required" />
+            <xsd:attribute name="rigidId" type="xsd:NCName" use="required" />
+            <xsd:attribute
+                name="behavior"
+                type="tns:RigidBehaviorType"
+                use="required" />
+        </xsd:complexType>
+
+        <xsd:unique name="ThroughRigidGroup">
+            <xsd:selector xpath="./tns:throughRigidGroup" />
+            <xsd:field xpath="@rigidGroupIdRef" />
+        </xsd:unique>
+
+    </xsd:element>
+
+
+    <xsd:element name="linkedBone">
+        <xsd:annotation>
+            <xsd:documentation>
+                Referenced Bone from Rigid-body.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="boneIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidShapeSphere">
+        <xsd:annotation>
+            <xsd:documentation>
+                Sphere shape of Rigid-body.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="radius" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidShapeBox">
+        <xsd:annotation>
+            <xsd:documentation>
+                Box shape of Rigid-body.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="width"  type="xsd:float" use="required" />
+            <xsd:attribute name="height" type="xsd:float" use="required" />
+            <xsd:attribute name="depth"  type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidShapeCapsule">
+        <xsd:annotation>
+            <xsd:documentation>
+                Capsule shape of Rigid-body.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="height" type="xsd:float" use="required" />
+            <xsd:attribute name="radius" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="radRotation">
+        <xsd:annotation>
+            <xsd:documentation>
+                Rotaion information by radian.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="xRad" type="xsd:float" use="required" />
+            <xsd:attribute name="yRad" type="xsd:float" use="required" />
+            <xsd:attribute name="zRad" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="dynamics">
+        <xsd:annotation>
+            <xsd:documentation>
+                Dynamics parameters.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="mass"
+                type="xsd:float"
+                use="required" />
+            <xsd:attribute
+                name="dampingPosition"
+                type="xsd:float"
+                use="required" />
+            <xsd:attribute
+                name="dampingRotation"
+                type="xsd:float"
+                use="required" />
+            <xsd:attribute
+                name="restitution"
+                type="xsd:float"
+                use="required" />
+            <xsd:attribute
+                name="friction"
+                type="xsd:float"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="throughRigidGroup">
+        <xsd:annotation>
+            <xsd:documentation>
+                Reference for non-collision rigid-group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="rigidGroupIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidGroupList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of Rigid-body group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:rigidGroup"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:key name="RigidGroup-Id">
+            <xsd:selector xpath="./tns:rigidGroup" />
+            <xsd:field xpath="@rigidGroupId" />
+        </xsd:key>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidGroup">
+        <xsd:annotation>
+            <xsd:documentation>
+                Rigid-body group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:rigidGroupMember"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+
+            <xsd:attribute
+                name="rigidGroupId"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+        <xsd:unique name="RigidGroupMember">
+            <xsd:selector xpath="./tns:rigidGroupMember" />
+            <xsd:field xpath="@rigidIdRef" />
+        </xsd:unique>
+
+    </xsd:element>
+
+
+    <xsd:element name="rigidGroupMember">
+        <xsd:annotation>
+            <xsd:documentation>
+                Member of Rigid-body group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="rigidIdRef"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="jointList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of joint definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:joint"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="joint">
+        <xsd:annotation>
+            <xsd:documentation>
+                Joint definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:i18nName"
+                    minOccurs="0" maxOccurs="unbounded" />
+                <xsd:element ref="tns:jointedRigidPair" />
+                <xsd:element ref="tns:position" />
+                <xsd:element ref="tns:limitPosition" />
+                <xsd:element ref="tns:radRotation" />
+                <xsd:element ref="tns:limitRotation" />
+                <xsd:element ref="tns:elasticPosition" />
+                <xsd:element ref="tns:elasticRotation" />
+            </xsd:sequence>
+
+            <xsd:attribute
+                name="name"
+                type="xsd:string"
+                use="required" />
+        </xsd:complexType>
+
+<!--
+        <xsd:unique name="JointName">
+            <xsd:selector xpath="./tns:joint" />
+            <xsd:field xpath="@name" />
+        </xsd:unique>
+-->
+    </xsd:element>
+
+
+    <xsd:element name="jointedRigidPair">
+        <xsd:annotation>
+            <xsd:documentation>
+                Jointed-bones information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="rigidIdRef1"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="rigidIdRef2"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="limitPosition">
+        <xsd:annotation>
+            <xsd:documentation>
+                Limit of Position.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="xFrom" type="xsd:float" use="required" />
+            <xsd:attribute name="xTo"   type="xsd:float" use="required" />
+            <xsd:attribute name="yFrom" type="xsd:float" use="required" />
+            <xsd:attribute name="yTo"   type="xsd:float" use="required" />
+            <xsd:attribute name="zFrom" type="xsd:float" use="required" />
+            <xsd:attribute name="zTo"   type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="limitRotation">
+        <xsd:annotation>
+            <xsd:documentation>
+                Limit of Rotation.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="xFrom" type="xsd:float" use="required" />
+            <xsd:attribute name="xTo"   type="xsd:float" use="required" />
+            <xsd:attribute name="yFrom" type="xsd:float" use="required" />
+            <xsd:attribute name="yTo"   type="xsd:float" use="required" />
+            <xsd:attribute name="zFrom" type="xsd:float" use="required" />
+            <xsd:attribute name="zTo"   type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="elasticPosition">
+        <xsd:annotation>
+            <xsd:documentation>
+                Elastic position of joint.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="x" type="xsd:float" use="required" />
+            <xsd:attribute name="y" type="xsd:float" use="required" />
+            <xsd:attribute name="z" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="elasticRotation">
+        <xsd:annotation>
+            <xsd:documentation>
+                Elastic rotation of joint by degree.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="xDeg" type="xsd:float" use="required" />
+            <xsd:attribute name="yDeg" type="xsd:float" use="required" />
+            <xsd:attribute name="zDeg" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="surfaceGroupList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of surface group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element
+                    ref="tns:surfaceGroup"
+                    minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:key name="SurfaceGroup-Id">
+            <xsd:selector xpath="./tns:surfaceGroup" />
+            <xsd:field xpath="@surfaceGroupId" />
+        </xsd:key>
+
+    </xsd:element>
+
+
+    <xsd:element name="surfaceGroup">
+        <xsd:annotation>
+            <xsd:documentation>
+                Surface group.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:surface" minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+
+            <xsd:attribute
+                name="surfaceGroupId"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="surface">
+        <xsd:annotation>
+            <xsd:documentation>
+                Each surface with Triangle.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute
+                name="vtxIdRef1"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="vtxIdRef2"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="vtxIdRef3"
+                type="xsd:NCName"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="vertexList">
+        <xsd:annotation>
+            <xsd:documentation>
+                List of vertex.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:vertex" minOccurs="0" maxOccurs="unbounded" />
+            </xsd:sequence>
+        </xsd:complexType>
+
+        <xsd:key name="Vertex-Id">
+            <xsd:selector xpath="./tns:vertex" />
+            <xsd:field xpath="@vtxId" />
+        </xsd:key>
+
+    </xsd:element>
+
+
+    <xsd:element name="vertex">
+        <xsd:annotation>
+            <xsd:documentation>
+                Vertex definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element ref="tns:position" />
+                <xsd:element ref="tns:normal" />
+                <xsd:element ref="tns:uvMap" />
+                <xsd:element ref="tns:skinning" />
+            </xsd:sequence>
+
+            <xsd:attribute
+                name="vtxId"
+                type="xsd:NCName"
+                use="required" />
+            <xsd:attribute
+                name="showEdge"
+                type="xsd:boolean"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="position">
+        <xsd:annotation>
+            <xsd:documentation>
+                Position definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="x" type="xsd:float" use="required" />
+            <xsd:attribute name="y" type="xsd:float" use="required" />
+            <xsd:attribute name="z" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="normal">
+        <xsd:annotation>
+            <xsd:documentation>
+                Normal vector definition.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="x" type="xsd:float" use="required" />
+            <xsd:attribute name="y" type="xsd:float" use="required" />
+            <xsd:attribute name="z" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="uvMap">
+        <xsd:annotation>
+            <xsd:documentation>
+                UV-mapping information.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="u" type="xsd:float" use="required" />
+            <xsd:attribute name="v" type="xsd:float" use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+    <xsd:element name="skinning">
+        <xsd:annotation>
+            <xsd:documentation>
+                Skinning definition from vertex to bone.
+            </xsd:documentation>
+        </xsd:annotation>
+
+        <xsd:complexType>
+            <xsd:attribute name="boneIdRef1" type="xsd:NCName" use="required" />
+            <xsd:attribute name="boneIdRef2" type="xsd:NCName" use="required" />
+            <xsd:attribute
+                name="weightBalance"
+                type="xsd:nonNegativeInteger"
+                use="required" />
+        </xsd:complexType>
+
+    </xsd:element>
+
+
+</xsd:schema>
+
+
+<!-- EOF -->