OSDN Git Service

vmdパッケージ導入
authorOlyutorskii <olyutorskii@users.osdn.me>
Tue, 23 Aug 2011 16:12:37 +0000 (01:12 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Tue, 23 Aug 2011 16:12:37 +0000 (01:12 +0900)
43 files changed:
src/main/java/jp/sourceforge/mikutoga/vmd/AbstractNumbered.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/FrameNumbered.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/IllegalVmdDataException.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/VmdConst.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/BezierParam.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/BoneMotion.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/CameraMotion.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/CameraRotation.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousColor.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousMotion.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousVector.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/MorphMotion.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/NamedListMap.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/PosCurve.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/ShadowMode.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/ShadowMotion.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/VmdMotion.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/BasicExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/BasicLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/CameraExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/CameraLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/LightingExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/LightingLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/VmdExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/VmdLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/VmdXmlExporter.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/VmdXmlResources.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/Xml.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/Xml2VmdLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/XmlCameraLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/XmlLightingLoader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdBasicHandler.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdBasicParser.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdCameraHandler.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdCameraParser.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdLightingHandler.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdLightingParser.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdParser.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/vmd/parser/package-info.java [new file with mode: 0644]

diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/AbstractNumbered.java b/src/main/java/jp/sourceforge/mikutoga/vmd/AbstractNumbered.java
new file mode 100644 (file)
index 0000000..23935cd
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * implemented FrameNumbered
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd;
+
+/**
+ * FrameNumberedの抽象実装クラス。
+ */
+public abstract class AbstractNumbered implements FrameNumbered {
+
+    private int frameNo = 0;
+
+
+    /**
+     * コンストラクタ。
+     */
+    protected AbstractNumbered(){
+        super();
+        return;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * @param num {@inheritDoc}
+     */
+    @Override
+    public void setFrameNumber(int num){
+        this.frameNo = num;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getFrameNumber(){
+        return this.frameNo;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/FrameNumbered.java b/src/main/java/jp/sourceforge/mikutoga/vmd/FrameNumbered.java
new file mode 100644 (file)
index 0000000..7e7fabc
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * frame-numbered interface
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd;
+
+import java.util.Comparator;
+
+/**
+ * キーフレーム番号を持つオブジェクトの抽象化インタフェース。
+ */
+public interface FrameNumbered {
+
+    /** 昇順での比較子。 */
+    Comparator<FrameNumbered> COMPARATOR = new FrameComparator();
+
+    /**
+     * フレーム番号を設定する。
+     * @param num フレーム番号
+     */
+    void setFrameNumber(int num);
+
+    /**
+     * フレーム番号を返す。
+     * @return フレーム番号
+     */
+    int getFrameNumber();
+
+
+    /**
+     * フレーム番号による比較子Comparator。
+     * フレーム番号の昇順を定義づける。
+     */
+    @SuppressWarnings("serial")
+    class FrameComparator
+            implements Comparator<FrameNumbered> {
+
+        /**
+         * コンストラクタ。
+         */
+        public FrameComparator(){
+            super();
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param o1 {@inheritDoc}
+         * @param o2 {@inheritDoc}
+         * @return {@inheritDoc}
+         */
+        @Override
+        @SuppressWarnings("PMD.CompareObjectsWithEquals")
+        public int compare(FrameNumbered o1, FrameNumbered o2){
+            if(o1 == o2) return 0;
+            if(o1 == null) return -1;
+            if(o2 == null) return +1;
+
+            int num1 = o1.getFrameNumber();
+            int num2 = o2.getFrameNumber();
+
+            return num1 - num2;
+        }
+
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/IllegalVmdDataException.java b/src/main/java/jp/sourceforge/mikutoga/vmd/IllegalVmdDataException.java
new file mode 100644 (file)
index 0000000..d1b4caa
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * illegal VMD motion data exception
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd;
+
+/**
+ * VMDモーションデータの不備を発見した場合の例外。
+ */
+@SuppressWarnings("serial")
+public class IllegalVmdDataException extends Exception{
+
+    /**
+     * コンストラクタ。
+     */
+    public IllegalVmdDataException(){
+        super();
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param message 詳細メッセージ
+     */
+    public IllegalVmdDataException(String message){
+        super(message);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param cause 原因
+     */
+    public IllegalVmdDataException(Throwable cause){
+        super(cause);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param message 詳細メッセージ
+     * @param cause 原因
+     */
+    public IllegalVmdDataException(String message, Throwable cause){
+        super(message, cause);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/VmdConst.java b/src/main/java/jp/sourceforge/mikutoga/vmd/VmdConst.java
new file mode 100644 (file)
index 0000000..e2a10c4
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * VMD constant data
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd;
+
+import java.nio.charset.Charset;
+
+/**
+ * VMDファイルフォーマットの各種定数。
+ */
+public final class VmdConst {
+
+    /**
+     * VMDファイルヘッダ長。バイト単位。
+     */
+    public static final int HEADER_LENGTH = 30;
+
+    /**
+     * ファイルヘッダ部先頭のASCIIコード相当部。
+     */
+    public static final String MAGIC_TXT = "Vocaloid Motion Data 0002";
+
+    /**
+     * モデル名最大長。バイト単位。
+     */
+    public static final int MODELNAME_MAX = 20;
+
+    /**
+     * ボーン名最大長。バイト単位。
+     */
+    public static final int BONENAME_MAX = 15;
+
+    /**
+     * モーフ名最大長。バイト単位。
+     */
+    public static final int MORPHNAME_MAX = 15;
+
+    /**
+     * カメラもしくはライティングデータに便宜上割り当てられるモデル名。
+     */
+    public static final String MODELNAME_STAGEACT =
+            "\u30ab\u30e1\u30e9\u30fb\u7167\u660e"; // "カメラ・照明";
+
+    /**
+     * 便宜上割り振られる特殊なモーフ名。
+     */
+    public static final String MORPHNAME_BASE = "base";
+
+    /**
+     * ボーンモーションデータの個別サイズ。バイト単位。
+     */
+    public static final int BONEMOTION_DATA_SZ = 111;
+
+    /**
+     * モーフデータの個別サイズ。バイト単位。
+     */
+    public static final int MORPH_DATA_SZ = 23;
+
+    /**
+     * カメラデータの個別サイズ。バイト単位。
+     */
+    public static final int CAMERA_DATA_SZ = 61;
+
+    /**
+     * 光源データの個別サイズ。バイト単位。
+     */
+    public static final int LUMINOUS_DATA_SZ = 28;
+
+    /**
+     * 影演出データの個別サイズ。バイト単位。
+     */
+    public static final int SHADOW_DATA_SZ = 9;
+
+
+    private static final Charset CS_ASCII = Charset.forName("US-ASCII");
+    private static final byte[] MAGIC_BYTES;
+
+
+    static{
+        MAGIC_BYTES = createMagicHeader();
+        assert MAGIC_BYTES.length <= HEADER_LENGTH;
+
+        assert "カメラ・照明".equals(MODELNAME_STAGEACT);
+    }
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private VmdConst(){
+        super();
+        assert false;
+        throw new AssertionError();
+    }
+
+    /**
+     * ヘッダ先頭のマジックバイト列を生成する。
+     * @return マジックバイト列
+     */
+    public static byte[] createMagicHeader(){
+        byte[] result = (MAGIC_TXT + '\0').getBytes(CS_ASCII);
+        return result;
+    }
+
+    /**
+     * バイト列先頭がマジックヘッダで始まるか検査する。
+     * @param array 検査対象のバイト列。
+     * @return マジックヘッダが検出されればtrue。
+     */
+    public static boolean startsWithMagic(byte[] array){
+        if(MAGIC_BYTES.length > array.length) return false;
+
+        for(int idx = 0; idx < MAGIC_BYTES.length; idx++){
+            if(array[idx] != MAGIC_BYTES[idx]) return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * カメラやライティングなどのステージ演出データの可能性があるか、
+     * モデル名から推測する。
+     * モデル名が"カメラ・照明"である場合、
+     * そのモーションファイルはほぼ
+     * カメラ・ライティング用ステージ演出データであると推測される。
+     * @param modelName モデル名
+     * @return モデル名にカメラもしくはライティングの可能性があるならtrue
+     */
+    public static boolean isStageActName(String modelName){
+        if(MODELNAME_STAGEACT.equals(modelName)) return true;
+        return false;
+    }
+
+    /**
+     * 特殊なモーフ名「base」か否か判定する。
+     * @param morphName モーフ名
+     * @return モーフ名が「base」ならtrue
+     */
+    public static boolean isBaseMorphName(String morphName){
+        if(MORPHNAME_BASE.equals(morphName)) return true;
+        return false;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/BezierParam.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/BezierParam.java
new file mode 100644 (file)
index 0000000..f90e88a
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * 3D bezier intaerpolation curve params
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/**
+ * 三次ベジェ曲線による補間カーブを記述する。
+ * <p>制御点P0,P1,P2,P3の座標により記述される。
+ * <p>P0は(0,0)で固定。P3は(127,127)で固定。
+ * 残りのP1,P2は、P0,P3を対角線とする正方形の内部に位置しなければならない。
+ */
+public class BezierParam {
+
+    /**
+     * 制御点P1のXデフォルト値(直線補間)。
+     */
+    public static final byte DEF_P1X = 20;
+    /**
+     * 制御点P1のYデフォルト値(直線補間)。
+     */
+    public static final byte DEF_P1Y = 20;
+    /**
+     * 制御点P2のXデフォルト値(直線補間)。
+     */
+    public static final byte DEF_P2X = 107;
+    /**
+     * 制御点P2のYデフォルト値(直線補間)。
+     */
+    public static final byte DEF_P2Y = 107;
+
+    /**
+     * 制御点P1のXデフォルト値(EaseInOut)。
+     */
+    public static final byte EIO_P1X = 64;
+    /**
+     * 制御点P1のYデフォルト値(EaseInOut)。
+     */
+    public static final byte EIO_P1Y =  0;
+    /**
+     * 制御点P2のXデフォルト値(EaseInOut)。
+     */
+    public static final byte EIO_P2X = 64;
+    /**
+     * 制御点P2のYデフォルト値(EaseInOut)。
+     */
+    public static final byte EIO_P2Y = 127;
+
+    /**
+     * 制御点空間最小値。
+     */
+    public static final byte MIN_VAL = 0;
+
+    /**
+     * 制御点空間最大値。
+     */
+    public static final byte MAX_VAL = 127;
+
+
+    private byte p1x = DEF_P1X;
+    private byte p1y = DEF_P1Y;
+    private byte p2x = DEF_P2X;
+    private byte p2y = DEF_P2Y;
+
+
+    /**
+     * コンストラクタ。
+     * <p>デフォルトの直線補間が設定される。
+     */
+    public BezierParam(){
+        super();
+        return;
+    }
+
+    /**
+     * 制御点P1のX座標を返す。
+     * @return 制御点P1のX座標
+     */
+    public byte getP1x() {
+        return this.p1x;
+    }
+
+    /**
+     * 制御点P1のY座標を返す。
+     * @return 制御点P1のY座標
+     */
+    public byte getP1y() {
+        return this.p1y;
+    }
+
+    /**
+     * 制御点P2のX座標を返す。
+     * @return 制御点P2のX座標
+     */
+    public byte getP2x() {
+        return this.p2x;
+    }
+
+    /**
+     * 制御点P2のY座標を返す。
+     * @return 制御点P2のY座標
+     */
+    public byte getP2y() {
+        return this.p2y;
+    }
+
+    /**
+     * 制御点P1のX座標を設定する。
+     * @param p1x 制御点P1のX座標
+     */
+    public void setP1x(byte p1x) {
+        this.p1x = p1x;
+        return;
+    }
+
+    /**
+     * 制御点P1のY座標を設定する。
+     * @param p1y 制御点P1のY座標
+     */
+    public void setP1y(byte p1y) {
+        this.p1y = p1y;
+        return;
+    }
+
+    /**
+     * 制御点P2のX座標を設定する。
+     * @param p2x 制御点P2のX座標
+     */
+    public void setP2x(byte p2x) {
+        this.p2x = p2x;
+        return;
+    }
+
+    /**
+     * 制御点P2のY座標を設定する。
+     * @param p2y 制御点P2のY座標
+     */
+    public void setP2y(byte p2y) {
+        this.p2y = p2y;
+        return;
+    }
+
+    /**
+     * 制御点P1の座標を設定する。
+     * @param p1xArg 制御点P1のX座標
+     * @param p1yArg 制御点P1のY座標
+     */
+    public void setP1(byte p1xArg, byte p1yArg) {
+        this.p1x = p1xArg;
+        this.p1y = p1yArg;
+        return;
+    }
+
+    /**
+     * 制御点P2の座標を設定する。
+     * @param p2xArg 制御点P2のX座標
+     * @param p2yArg 制御点P2のY座標
+     */
+    public void setP2(byte p2xArg, byte p2yArg) {
+        this.p2x = p2xArg;
+        this.p2y = p2yArg;
+        return;
+    }
+
+    /**
+     * 直線補間か判定する。
+     * <p>P1,P2双方がP0-P3対角線上に存在する場合を直線補間とする。
+     * @return 直線補間ならtrue
+     */
+    public boolean isLinear(){
+        if(this.p1x != this.p1y) return false;
+        if(this.p2x != this.p2y) return false;
+        return true;
+    }
+
+    /**
+     * MMDデフォルトの直線補間か判定する。
+     * @return MMDデフォルトの直線補間ならtrue
+     */
+    public boolean isDefaultLinear(){
+        if(this.p1x != DEF_P1X) return false;
+        if(this.p1y != DEF_P1Y) return false;
+
+        if(this.p2x != DEF_P2X) return false;
+        if(this.p2y != DEF_P2Y) return false;
+
+        assert isLinear();
+
+        return true;
+    }
+
+    /**
+     * MMDデフォルトのEaseInOutカーブか判定する。
+     * @return  MMDデフォルトのEaseInOutカーブならtrue
+     */
+    public boolean isDefaultEaseInOut(){
+        if(this.p1x != EIO_P1X) return false;
+        if(this.p1y != EIO_P1Y) return false;
+
+        if(this.p2x != EIO_P2X) return false;
+        if(this.p2y != EIO_P2Y) return false;
+
+        return true;
+    }
+
+    /**
+     * MMDデフォルトの直線補間に設定する。
+     */
+    public void setDefaultLinear(){
+        this.p1x = DEF_P1X;
+        this.p1y = DEF_P1Y;
+        this.p2x = DEF_P2X;
+        this.p2y = DEF_P2Y;
+        return;
+    }
+
+    /**
+     * MMDデフォルトのEaseInOutカーブに設定する。
+     */
+    public void setDefaultEaseInOut(){
+        this.p1x = EIO_P1X;
+        this.p1y = EIO_P1Y;
+        this.p2x = EIO_P2X;
+        this.p2y = EIO_P2Y;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        String delim = ", ";
+        result.append("P1=(")
+                .append(this.p1x).append(delim).append(this.p1y).append(") ");
+        result.append("P2=(")
+                .append(this.p2x).append(delim).append(this.p2y).append(")");
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/BoneMotion.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/BoneMotion.java
new file mode 100644 (file)
index 0000000..42e564b
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * bone motion
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkQuat;
+import jp.sourceforge.mikutoga.vmd.AbstractNumbered;
+
+/**
+ * ボーンのモーション情報。
+ * <p>ボーン名、ボーン位置、ボーン回転量、及び補間カーブ情報を持つ。
+ */
+public class BoneMotion extends AbstractNumbered {
+
+    private String boneName;
+
+    private final MkQuat rotation = new MkQuat();
+    private final BezierParam intpltRotation = new BezierParam();
+
+    private final MkPos3D position = new MkPos3D();
+    private final PosCurve posCurve = new PosCurve();
+
+
+    /**
+     * コンストラクタ。
+     */
+    public BoneMotion(){
+        super();
+        return;
+    }
+
+
+    /**
+     * ボーン名を返す。
+     * @return ボーン名
+     */
+    public String getBoneName(){
+        return this.boneName;
+    }
+
+    /**
+     * ボーン名を設定する。
+     * @param boneName ボーン名
+     */
+    public void setBoneName(String boneName){
+        this.boneName = boneName;
+        return;
+    }
+
+    /**
+     * ボーン回転量を返す。
+     * @return ボーン回転量
+     */
+    public MkQuat getRotation(){
+        return this.rotation;
+    }
+
+    /**
+     * ボーン回転の補間曲線情報を返す。
+     * @return ボーン回転の補間曲線情報
+     */
+    public BezierParam getIntpltRotation(){
+        return this.intpltRotation;
+    }
+
+    /**
+     * ボーン位置を返す。
+     * @return ボーン位置
+     */
+    public MkPos3D getPosition(){
+        return this.position;
+    }
+
+    /**
+     * ボーン位置移動の補間情報を返す。
+     * @return ボーン位置移動の補間情報
+     */
+    public PosCurve getPosCurve(){
+        return this.posCurve;
+    }
+
+    /**
+     * このモーションが暗黙の位置情報を持つか判定する。
+     * <p>ボーン位置が原点(0,0,0)にあり、
+     * XYZ3軸の移動補間カーブがデフォルト直線補間の場合、
+     * 暗黙の位置情報と見なされる。
+     * <p>MMDは、位置情報を持たないボーンのモーションに対し
+     * 便宜的にこの暗黙の位置情報を割り当てる。
+     * <p>通常の位置モーションが暗黙の位置情報と一致する場合もありうる。
+     * @return 暗黙の位置情報であるならtrue
+     */
+    public boolean hasImplicitPosition(){
+        if(this.position.isOriginPoint() && this.posCurve.isDefaultLinear()){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("bone name : [").append(this.boneName);
+        result.append("] #").append(getFrameNumber()).append('\n');
+
+        result.append("rotation ").append(this.rotation);
+        result.append(" R-Bezier ").append(this.intpltRotation).append('\n');
+
+        result.append("position ").append(this.position).append('\n');
+        result.append(this.posCurve);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/CameraMotion.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/CameraMotion.java
new file mode 100644 (file)
index 0000000..d418ca1
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * camera motion
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.vmd.AbstractNumbered;
+
+/**
+ * カメラモーション情報。
+ * <p>カメラの回転は極座標で表される。
+ * <p>カメラ-ターゲット間の距離は球座標(極座標)の動径に相当する。
+ * 通常はターゲットより手前に位置するカメラまでの距離が負の値で表される。
+ * カメラ位置がターゲットを突き抜けた場合は正の値もとりうる。
+ * ※MMDのUIと符号が逆なので注意。
+ * <p>パースペクティブモードがOFFの場合、
+ * 画角は無視され遠近感処理が行われなくなる。(平行投影?)
+ */
+public class CameraMotion extends AbstractNumbered {
+
+    private final MkPos3D cameraTarget = new MkPos3D();
+    private final PosCurve posCurve = new PosCurve();
+
+    private final CameraRotation cameraRotation = new CameraRotation();
+    private final BezierParam intpltRotation = new BezierParam();
+
+    private float range;
+    private final BezierParam intpltRange = new BezierParam();
+
+    private boolean hasPerspective;
+    private int projectionAngle;
+    private final BezierParam intpltProjection = new BezierParam();
+
+
+    /**
+     * コンストラクタ。
+     */
+    public CameraMotion(){
+        super();
+        return;
+    }
+
+
+    /**
+     * ターゲット位置情報を返す。
+     * @return ターゲット位置情報
+     */
+    public MkPos3D getCameraTarget(){
+        return this.cameraTarget;
+    }
+
+    /**
+     * ターゲット位置移動の補間情報を返す。
+     * @return ターゲット位置移動の補間情報
+     */
+    public PosCurve getTargetPosCurve(){
+        return this.posCurve;
+    }
+
+    /**
+     * カメラ回転情報を返す。
+     * @return カメラ回転情報
+     */
+    public CameraRotation getCameraRotation(){
+        return this.cameraRotation;
+    }
+
+    /**
+     * カメラ回転の補間曲線情報を返す。
+     * @return カメラ回転の補間曲線情報
+     */
+    public BezierParam getIntpltRotation(){
+        return this.intpltRotation;
+    }
+
+    /**
+     * カメラ-ターゲット間の距離を返す。
+     * @return カメラ-ターゲット間の距離
+     */
+    public float getRange(){
+        return this.range;
+    }
+
+    /**
+     * カメラ-ターゲット間の距離を設定する。
+     * @param range カメラ-ターゲット間の距離
+     */
+    public void setRange(float range){
+        this.range = range;
+        return;
+    }
+
+    /**
+     * カメラ-ターゲット間距離の補間曲線情報を返す。
+     * @return カメラ-ターゲット間距離の補間曲線情報
+     */
+    public BezierParam getIntpltRange(){
+        return this.intpltRange;
+    }
+
+    /**
+     * パースペクティブが有効か判定する。
+     * @return パースペクティブが有効ならtrue
+     */
+    public boolean hasPerspective(){
+        return this.hasPerspective;
+    }
+
+    /**
+     * パースペクティブモードを設定する。
+     * @param mode trueを渡すとパースペクティブが有効になる。
+     */
+    public void setPerspectiveMode(boolean mode){
+        this.hasPerspective = mode;
+        return;
+    }
+
+    /**
+     * 投影角度(スクリーン縦画角)を返す。
+     * @return 投影角度(度数法)
+     */
+    public int getProjectionAngle(){
+        return this.projectionAngle;
+    }
+
+    /**
+     * 投影角度(スクリーン縦画角)を設定する。
+     * @param angle 投影角度(度数法)
+     */
+    public void setProjectionAngle(int angle){
+        this.projectionAngle = angle;
+        return;
+    }
+
+    /**
+     * スクリーン投射の補間曲線情報を返す。
+     * @return スクリーン投射の補間曲線情報
+     */
+    public BezierParam getIntpltProjection(){
+        return this.intpltProjection;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("#").append(getFrameNumber()).append(' ');
+        result.append(this.cameraRotation);
+        result.append(" Rot-Bezier ")
+                .append(this.intpltRotation).append('\n');
+
+        result.append("range : ").append(this.range);
+        result.append(" Range-Bezier ").append(this.intpltRange).append('\n');
+
+        result.append("target-pos : ").append(this.cameraTarget).append('\n');
+        result.append(this.posCurve).append('\n');
+
+        result.append("perspective : ");
+        if(this.hasPerspective) result.append("ON");
+        else                    result.append("OFF");
+        result.append('\n');
+
+        result.append("projection angle : ").append(this.projectionAngle);
+        result.append("deg Bezier ").append(this.intpltProjection);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/CameraRotation.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/CameraRotation.java
new file mode 100644 (file)
index 0000000..958e87b
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * VMD camerawork handler
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/**
+ * カメラの回転情報。
+ * <p>latitudeはターゲットから見たカメラの仰俯角(≒緯度)。
+ * 単位はラジアン。
+ * Y軸回転量が0の時のZ正軸がY正軸へ倒れる方向が正回転。
+ * (MMDのUIとは符号が逆になるので注意)
+ * 仰俯角が0の場合、
+ * カメラはターゲットに対しXZ平面(水平)と平行な箇所に位置する。
+ * <p>longitudeはY軸周りの回転量(≒経度)。単位はラジアン。
+ * X正軸がZ正軸へ倒れる方向が正回転。(ボーン回転と逆)
+ * 仰俯角およびY軸回転量が0の場合、
+ * カメラレンズはZ軸-∞方向からZ軸+∞方向を向く。
+ * <p>rollはレンズをターゲットを向けたカメラのロール回転量。
+ * 仰俯角とY軸回転量が0の時にY正軸がX正軸に倒れる方向が正回転。
+ * 仰俯角およびロール回転量が0の場合、カメラ上部はY軸+∞の方を向く。
+ */
+public class CameraRotation {
+
+    private float latitude;
+    private float longitude;
+    private float roll;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public CameraRotation(){
+        super();
+        return;
+    }
+
+
+    /**
+     * ターゲットから見たカメラの仰俯角(≒緯度)を返す。
+     * @return ターゲットから見たカメラの仰俯角(≒緯度)
+     */
+    public float getLatitude(){
+        return this.latitude;
+    }
+
+    /**
+     * ターゲットから見たカメラの仰俯角(≒緯度)を設定する。
+     * @param latitude ターゲットから見たカメラの仰俯角(≒緯度)
+     */
+    public void setLatitude(float latitude){
+        this.latitude = latitude;
+        return;
+    }
+
+    /**
+     * Y軸周りの回転量(≒経度)を返す。
+     * @return Y軸周りの回転量(≒経度)
+     */
+    public float getLongitude(){
+        return this.longitude;
+    }
+
+    /**
+     * Y軸周りの回転量(≒経度)を設定する。
+     * @param longitude Y軸周りの回転量(≒経度)
+     */
+    public void setLongitude(float longitude){
+        this.longitude = longitude;
+        return;
+    }
+
+    /**
+     * レンズをターゲットを向けたカメラのロール回転量を返す。
+     * @return レンズをターゲットを向けたカメラのロール回転量
+     */
+    public float getRoll(){
+        return this.roll;
+    }
+
+    /**
+     * レンズをターゲットを向けたカメラのロール回転量を設定する。
+     * @param roll レンズをターゲットを向けたカメラのロール回転量
+     */
+    public void setRoll(float roll){
+        this.roll = roll;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("latitude=").append(this.latitude);
+        result.append(" longitude=").append(this.longitude);
+        result.append(" roll=").append(this.roll);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousColor.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousColor.java
new file mode 100644 (file)
index 0000000..b28b7c6
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * luminous color
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/**
+ * 光源の色設定。
+ * <p>色情報はRGB色空間で記述される。
+ * <p>MMDのUI上の各色成分指定0~255定義域に便宜上256を追加したものが、
+ * 0.0以上1.0以下にマップされる。
+ * <ul>
+ * <li>0は正しく0.0にマップされる。
+ * <li>128は正しく0.5にマップされる。
+ * <li>255は1.0より少しだけ小さい数(≒0.99609375)にマップされる。
+ * </ul>
+ */
+public class LuminousColor {
+
+    /** デフォルトの成分値。 */
+    public static final float DEF_BRIGHT = 0.602f; // ≒ (154.0 / 256.0)
+
+
+    private float colR = DEF_BRIGHT;
+    private float colG = DEF_BRIGHT;
+    private float colB = DEF_BRIGHT;
+
+
+    /**
+     * コンストラクタ。
+     * <p>MMDデフォルトの光源色(154,154,154)が設定される。
+     */
+    public LuminousColor(){
+        super();
+        return;
+    }
+
+
+    /**
+     * 光源の赤成分を設定する。
+     * @param colR 赤成分
+     */
+    public void setColR(float colR) {
+        this.colR = colR;
+        return;
+    }
+
+    /**
+     * 光源の緑成分を設定する。
+     * @param colG 緑成分
+     */
+    public void setColG(float colG) {
+        this.colG = colG;
+        return;
+    }
+
+    /**
+     * 光源の青成分を設定する。
+     * @param colB 青成分
+     */
+    public void setColB(float colB) {
+        this.colB = colB;
+        return;
+    }
+
+    /**
+     * 光源の赤成分を返す。
+     * @return 赤成分
+     */
+    public float getColR(){
+        return this.colR;
+    }
+
+    /**
+     * 光源の緑成分を返す。
+     * @return 緑成分
+     */
+    public float getColG(){
+        return this.colG;
+    }
+
+    /**
+     * 光源の青成分を返す。
+     * @return 青成分
+     */
+    public float getColB(){
+        return this.colB;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("r=").append(this.colR);
+        result.append(" g=").append(this.colG);
+        result.append(" b=").append(this.colB);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousMotion.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousMotion.java
new file mode 100644 (file)
index 0000000..b2eb05c
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * luminous motion
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import jp.sourceforge.mikutoga.vmd.AbstractNumbered;
+
+/**
+ * 光源演出情報。
+ */
+public class LuminousMotion extends AbstractNumbered {
+
+    private final LuminousColor color = new LuminousColor();
+    private final LuminousVector direction = new LuminousVector();
+
+
+    /**
+     * コンストラクタ。
+     */
+    public LuminousMotion(){
+        super();
+        return;
+    }
+
+
+    /**
+     * 光源の色情報を返す。
+     * @return 光源の色情報
+     */
+    public LuminousColor getColor(){
+        return this.color;
+    }
+
+    /**
+     * 光源からの照射方向情報を返す。
+     * @return 光源からの照射方向情報
+     */
+    public LuminousVector getDirection(){
+        return this.direction;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("#").append(getFrameNumber());
+        result.append(" luminous color : ").append(this.color);
+        result.append(" direction : ").append(this.direction);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousVector.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/LuminousVector.java
new file mode 100644 (file)
index 0000000..78f2a7f
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * luminous direction vector
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/**
+ * 光源からの照射方向を表す方向ベクトル。
+ * <p>照明方向は、
+ * ワールド座標原点から伸びる方向ベクトルとして記述される。
+ * この方向ベクトルに向けて、無限遠の光源から照明が当たる。
+ * <p>MMDのスライダUI上では各軸成分の定義域は-1.0以上+1.0以下だが、
+ * さらに絶対値の大きな値を指定することも可能。
+ * <p>方向ベクトルの長さは演出上の意味を持たないが、
+ * キーフレーム間の照明方向の補間に影響を及ぼすかもしれない。
+ * <p>方向ベクトルが零ベクトル(0,0,0)の場合、MMDでは全ポリゴンに影が落ちる。
+ */
+public class LuminousVector {
+
+    /** デフォルトのX成分。 */
+    public static final float DEF_VECX = -0.5f;
+    /** デフォルトのY成分。 */
+    public static final float DEF_VECY = -1.0f;
+    /** デフォルトのZ成分。 */
+    public static final float DEF_VECZ = +0.5f;
+
+
+    private float vecX = DEF_VECX;
+    private float vecY = DEF_VECY;
+    private float vecZ = DEF_VECZ;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public LuminousVector(){
+        super();
+        return;
+    }
+
+
+    /**
+     * 照射方向ベクトルのX成分を設定する。
+     * @param vecX ベクトルのX成分
+     */
+    public void setVecX(float vecX) {
+        this.vecX = vecX;
+        return;
+    }
+
+    /**
+     * 照射方向ベクトルのY成分を設定する。
+     * @param vecY ベクトルのY成分
+     */
+    public void setVecY(float vecY) {
+        this.vecY = vecY;
+        return;
+    }
+
+    /**
+     * 照射方向ベクトルのZ成分を設定する。
+     * @param vecZ ベクトルのZ成分
+     */
+    public void setVecZ(float vecZ) {
+        this.vecZ = vecZ;
+        return;
+    }
+
+    /**
+     * 照射方向ベクトルのX成分を返す。
+     * @return 方向ベクトルX成分
+     */
+    public float getVecX() {
+        return this.vecX;
+    }
+
+    /**
+     * 照射方向ベクトルのY成分を返す。
+     * @return 方向ベクトルY成分
+     */
+    public float getVecY() {
+        return this.vecY;
+    }
+
+    /**
+     * 照射方向ベクトルのZ成分を返す。
+     * @return 方向ベクトルZ成分
+     */
+    public float getVecZ() {
+        return this.vecZ;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("x=").append(this.vecX);
+        result.append(" y=").append(this.vecY);
+        result.append(" z=").append(this.vecZ);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/MorphMotion.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/MorphMotion.java
new file mode 100644 (file)
index 0000000..fe77f0d
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * morph motion
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import jp.sourceforge.mikutoga.vmd.AbstractNumbered;
+
+/**
+ * モーフ情報。
+ * <p>通常、モーフ量は0.0以上1.0以下の値をとる。
+ */
+public class MorphMotion extends AbstractNumbered {
+
+    private String morphName = "";
+    private float flex = 0.0f;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public MorphMotion(){
+        super();
+        return;
+    }
+
+
+    /**
+     * モーフ名を設定する。
+     * @param morphName モーフ名
+     */
+    public void setMorphName(String morphName) {
+        this.morphName = morphName;
+        return;
+    }
+
+    /**
+     * モーフ名を返す。
+     * @return モーフ名
+     */
+    public String getMorphName() {
+        return this.morphName;
+    }
+
+    /**
+     * モーフ変量を設定する。
+     * @param flex 変量
+     */
+    public void setFlex(float flex) {
+        this.flex = flex;
+        return;
+    }
+
+    /**
+     * モーフ変量を返す。
+     * @return 変量
+     */
+    public float getFlex() {
+        return this.flex;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("morph name : [").append(this.morphName);
+        result.append("] #").append(getFrameNumber());
+        result.append(" flex = ").append(this.flex);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/NamedListMap.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/NamedListMap.java
new file mode 100644 (file)
index 0000000..01aedfc
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * string named list map
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 名前付けされたリストのマップ。
+ * 登録名の追加順が保持される。
+ * @param <E> リスト要素の型
+ */
+public class NamedListMap<E> {
+
+    private final List<String> nameList;
+    private final Map<String, List<E>> listMap;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public NamedListMap(){
+        super();
+        this.nameList = new LinkedList<String>();
+        this.listMap = new HashMap<String, List<E>>();
+        return;
+    }
+
+
+    /**
+     * マップをクリアする。
+     */
+    public void clear(){
+        this.nameList.clear();
+        this.listMap.clear();
+        return;
+    }
+
+    /**
+     * マップが空か否か判定する。
+     * @return 空ならtrue
+     */
+    public boolean isEmpty(){
+        if(this.listMap.isEmpty()) return true;
+        return false;
+    }
+
+    /**
+     * 名前一覧を返す。
+     * <p>名前は登録順に並ぶ。
+     * <p>ここで返されるListへの修正操作は不可能。
+     * @return 名前一覧のリスト
+     */
+    public List<String> getNames(){
+        List<String> result = Collections.unmodifiableList(this.nameList);
+        return result;
+    }
+
+    /**
+     * 名前付けされたリストを返す。
+     * @param name 名前
+     * @return 名前付けされたリスト。リストが存在しなければnull。
+     */
+    public List<E> getNamedList(String name){
+        List<E> result = this.listMap.get(name);
+        return result;
+    }
+
+    /**
+     * 名前付けされたリストを削除する。
+     * 存在しない名前が渡された場合、何もしない。
+     * @param name 名前
+     */
+    public void removeNamedList(String name){
+        if(this.listMap.remove(name) == null) return;
+        this.nameList.remove(name);
+        return;
+    }
+
+    /**
+     * 名前付けされたリストに新要素を追加する。
+     * 未登録の名前であれば新たにリストが作成される。
+     * @param name 名前
+     * @param elem 新要素
+     * @throws NullPointerException 引数がnull
+     */
+    public void addNamedElement(String name, E elem)
+            throws NullPointerException{
+        if(name == null || elem == null) throw new NullPointerException();
+
+        List<E> list = this.listMap.get(name);
+        if(list == null){
+            list = new LinkedList<E>();
+            this.listMap.put(name, list);
+            this.nameList.add(name);
+        }
+
+        list.add(elem);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/PosCurve.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/PosCurve.java
new file mode 100644 (file)
index 0000000..6cf489c
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * position curve
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/**
+ * 3次元位置移動の補間情報。
+ * <p>XYZ3軸それぞれに対応するベジェ曲線を3本持つ。
+ */
+public class PosCurve {
+
+    private final BezierParam intpltXpos = new BezierParam();
+    private final BezierParam intpltYpos = new BezierParam();
+    private final BezierParam intpltZpos = new BezierParam();
+
+
+    /**
+     * コンストラクタ。
+     */
+    public PosCurve(){
+        super();
+        return;
+    }
+
+
+    /**
+     * ボーンX軸移動の補間曲線情報を返す。
+     * @return ボーンX軸移動の補間曲線情報
+     */
+    public BezierParam getIntpltXpos(){
+        return this.intpltXpos;
+    }
+
+    /**
+     * ボーンY軸移動の補間曲線情報を返す。
+     * @return ボーンY軸移動の補間曲線情報
+     */
+    public BezierParam getIntpltYpos(){
+        return this.intpltYpos;
+    }
+
+    /**
+     * ボーンZ軸移動の補間曲線情報を返す。
+     * @return ボーンZ軸移動の補間曲線情報
+     */
+    public BezierParam getIntpltZpos(){
+        return this.intpltZpos;
+    }
+
+    /**
+     * インデックス指定された各軸の補間曲線情報を返す。
+     * <p>インデックス値0がX軸、1がY軸、2がZ軸に対応する。
+     * @param no インデックス値[0-2]
+     * @return 各軸の補間曲線情報
+     * @throws IllegalArgumentException インデックス値が範囲外
+     */
+    public BezierParam item(int no) throws IllegalArgumentException {
+        BezierParam result;
+        switch(no){
+        case 0: result = this.intpltXpos; break;
+        case 1: result = this.intpltYpos; break;
+        case 2: result = this.intpltZpos; break;
+        default:
+            throw new IllegalArgumentException();
+        }
+        return result;
+    }
+
+    /**
+     * 3軸ともMMDデフォルトの直線補間か判定する。
+     * @return 3軸ともMMDデフォルトの直線補間ならtrue
+     */
+    public boolean isDefaultLinear(){
+        if( ! this.intpltXpos.isDefaultLinear() ) return false;
+        if( ! this.intpltYpos.isDefaultLinear() ) return false;
+        if( ! this.intpltZpos.isDefaultLinear() ) return false;
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("X-Bezier ").append(this.intpltXpos).append('\n');
+        result.append("Y-Bezier ").append(this.intpltYpos).append('\n');
+        result.append("Z-Bezier ").append(this.intpltZpos);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/ShadowMode.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/ShadowMode.java
new file mode 100644 (file)
index 0000000..2cac757
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * shadow mode
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/**
+ * セルフシャドウ描画モード。
+ */
+public enum ShadowMode {
+
+    /**
+     * シャドウを描画しないモード。
+     */
+    NONE(0x00),
+
+    /**
+     * シャドウ描画の質がカメラからの距離の影響をあまり受けないモード。
+     * <p>※MMDのUIにおけるデフォルト。
+     */
+    MODE_1(0x01),
+
+    /**
+     * シャドウ描画の質をカメラからの距離に応じて劣化させることにより、
+     * カメラに近いオブジェクトのシャドウ描画の質を向上させるモード。
+     */
+    MODE_2(0x02),
+
+    ;
+
+
+    private static final int MASK_8 = 0xff;
+
+
+    private final byte code;
+
+
+    /**
+     * コンストラクタ。
+     * @param code 割り当て番号(byte)
+     */
+    private ShadowMode(byte code){
+        this.code = code;
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param iVal 割り当て番号(int)
+     * ※上位24bitは捨てられる。
+     */
+    private ShadowMode(int iVal){
+        this((byte)(iVal & MASK_8));
+        return;
+    }
+
+
+    /**
+     * byte型値に符号化された時の値を返す。
+     * @return byte型値に符号化された時の値
+     */
+    public byte getEncodedByte(){
+        return this.code;
+    }
+
+    /**
+     * byte型値から描画モードを復号する。
+     * @param bVal byte型値
+     * @return 描画モード。未定義の値が与えられた場合はnull。
+     */
+    public static ShadowMode decode(byte bVal){
+        ShadowMode result = null;
+
+        if     (bVal == NONE  .code) result = NONE;
+        else if(bVal == MODE_1.code) result = MODE_1;
+        else if(bVal == MODE_2.code) result = MODE_2;
+
+        return result;
+    }
+
+    /**
+     * int型値から描画モードを復号する。
+     * <p>上位24bitは無視される。
+     * @param iVal int型値
+     * @return 描画モード。未定義の値が与えられた場合はnull。
+     */
+    public static ShadowMode decode(int iVal){
+        return decode((byte)(iVal & MASK_8));
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/ShadowMotion.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/ShadowMotion.java
new file mode 100644 (file)
index 0000000..85c502f
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * shadow motion
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import jp.sourceforge.mikutoga.vmd.AbstractNumbered;
+
+/**
+ * 影(セルフシャドウ)演出情報。
+ * <p>カメラからの距離情報(幾何距離の100倍?)による影演出対象の範囲指定は、
+ * MMDのスライダUI上では0から9999までが指定可能。
+ * <p>MMDのスライダUI値SからVMDファイル上の生パラメターへの変換式は、
+ * 「 0.1 - (S / 1.0E+5) 」
+ * となる。
+ */
+public class ShadowMotion extends AbstractNumbered {
+
+    /**
+     * デフォルトの影描画モード。
+     */
+    public static final ShadowMode DEF_MODE = ShadowMode.MODE_1;
+
+    /**
+     * デフォルトの範囲指定生パラメータ。
+     * <p>MMDのスライダUI値「8875」にほぼ相当。
+     */
+    public static final float DEF_SCOPE = 0.01125f;
+
+    private static final double OFFSET = 0.1;
+    private static final double SCALE = 1.0E+5;
+
+
+    private ShadowMode shadowMode = DEF_MODE;
+    private float rawScopeParam = DEF_SCOPE;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public ShadowMotion(){
+        super();
+        return;
+    }
+
+
+    /**
+     * VMDファイル上の生パラメータ数値による演出対象範囲指定を、
+     * MMDのUI上の距離情報(カメラからの幾何距離×100倍?)に変換する。
+     * @param param 生パラメータ
+     * @return MMDのスライダUI上の距離情報
+     */
+    public static double rawParamToScope(float param){
+        double result;
+        result = OFFSET - param;
+        result *= SCALE;
+        return result;
+    }
+
+    /**
+     * MMDのUI上の距離情報(カメラからの幾何距離×100倍?)を、
+     * VMDファイル上の生パラメータ数値に変換する。
+     * @param scope MMDのスライダUI上の距離情報
+     * @return 生パラメータ
+     */
+    public static float scopeToRawParam(double scope){
+        double result;
+        result = scope / SCALE;
+        result = OFFSET - result;
+        return (float) result;
+    }
+
+
+    /**
+     * 影演出の範囲指定の生パラメータを設定する。
+     * @param rawScopeParam 生パラメータ
+     */
+    public void setRawScopeParam(float rawScopeParam) {
+        this.rawScopeParam = rawScopeParam;
+        return;
+    }
+
+    /**
+     * 影演出の範囲指定の生パラメータを返す。
+     * @return 生パラメータ
+     */
+    public float getRawScopeParam() {
+        return this.rawScopeParam;
+    }
+
+    /**
+     * 影演出の範囲指定のスライダUI値を設定する。
+     * @param scope スライダUI値
+     */
+    public void setScope(double scope){
+        float rawVal = scopeToRawParam(scope);
+        setRawScopeParam(rawVal);
+        return;
+    }
+
+    /**
+     * 影演出の範囲指定のスライダUI値を返す。
+     * @return スライダUI値
+     */
+    public double getScope(){
+        float rawVal = getRawScopeParam();
+        double scope = rawParamToScope(rawVal);
+        return scope;
+    }
+
+    /**
+     * 影描画モードを設定する。
+     * @param shadowMode 影描画モード
+     * @throws NullPointerException 引数がnull
+     */
+    public void setShadowMode(ShadowMode shadowMode)
+            throws NullPointerException{
+        if(shadowMode == null) throw new NullPointerException();
+        this.shadowMode = shadowMode;
+        return;
+    }
+
+    /**
+     * 影描画モードを返す。
+     * @return 影描画モード
+     */
+    public ShadowMode getShadowMode(){
+        return this.shadowMode;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+
+        result.append("#").append(getFrameNumber());
+        result.append(" shadow mode : ").append(this.shadowMode);
+        result.append(" rawparam=").append(this.rawScopeParam);
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/VmdMotion.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/VmdMotion.java
new file mode 100644 (file)
index 0000000..36812ff
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * motion & stage act
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import jp.sourceforge.mikutoga.vmd.FrameNumbered;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+
+/**
+ * モーション及び演出情報。
+ */
+public class VmdMotion {
+
+    private String modelName = VmdConst.MODELNAME_STAGEACT;
+
+    private final NamedListMap<BoneMotion> bonePartMap;
+    private final NamedListMap<MorphMotion> morphPartMap;
+
+    private final List<CameraMotion>   cameraMotionList;
+    private final List<LuminousMotion> luminousMotionList;
+    private final List<ShadowMotion>   shadowMotionList;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public VmdMotion(){
+        super();
+
+        this.bonePartMap  = new NamedListMap<BoneMotion>();
+        this.morphPartMap = new NamedListMap<MorphMotion>();
+
+        this.cameraMotionList   = new LinkedList<CameraMotion>();
+        this.luminousMotionList = new LinkedList<LuminousMotion>();
+        this.shadowMotionList   = new LinkedList<ShadowMotion>();
+
+        return;
+    }
+
+
+    /**
+     * モデル名を返す。
+     * @return モデル名
+     */
+    public String getModelName(){
+        return this.modelName;
+    }
+
+    /**
+     * モデル名を設定する。
+     * <p>このモーションがモデルモーションかステージ演出情報かは、
+     * このモデル名で判別される。
+     * @param modelName モデル名
+     * @throws NullPointerException 引数がnull
+     */
+    public void setModelName(String modelName) throws NullPointerException{
+        if(modelName == null) throw new NullPointerException();
+        this.modelName = modelName;
+        return;
+    }
+
+    /**
+     * モデルモーションか否か判別する。
+     * <p>判別は特殊なモデル名を持つか否かで決定される。
+     * @return モデルモーションならtrue
+     */
+    public boolean isModelMotion(){
+        if(VmdConst.isStageActName(this.modelName)){
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 名前付きボーンモーションマップを返す。
+     * @return 名前付きボーンモーションマップ
+     */
+    public NamedListMap<BoneMotion> getBonePartMap(){
+        return this.bonePartMap;
+    }
+
+    /**
+     * 名前付きモーフモーションマップを返す。
+     * @return 名前付きモーフモーションマップ
+     */
+    public NamedListMap<MorphMotion> getMorphPartMap(){
+        return this.morphPartMap;
+    }
+
+    /**
+     * カメラモーションのリストを返す。
+     * @return カメラモーションのリスト
+     */
+    public List<CameraMotion> getCameraMotionList(){
+        return this.cameraMotionList;
+    }
+
+    /**
+     * 照明モーションのリストを返す。
+     * @return 照明モーションのリスト
+     */
+    public List<LuminousMotion> getLuminousMotionList(){
+        return this.luminousMotionList;
+    }
+
+    /**
+     * シャドウモーションのリストを返す。
+     * @return シャドウモーションのリスト
+     */
+    public List<ShadowMotion> getShadowMotionList(){
+        return this.shadowMotionList;
+    }
+
+    /**
+     * ボーンモーションを追加する。
+     * @param motion ボーンモーション
+     */
+    public void addBoneMotion(BoneMotion motion){
+        String name = motion.getBoneName();
+        this.bonePartMap.addNamedElement(name, motion);
+        return;
+    }
+
+    /**
+     * モーフモーションを追加する。
+     * @param motion モーフモーション
+     */
+    public void addMorphMotion(MorphMotion motion){
+        String name = motion.getMorphName();
+        this.morphPartMap.addNamedElement(name, motion);
+        return;
+    }
+
+    /**
+     * 各データをフレーム番号順に昇順ソートする。
+     */
+    public void frameSort(){
+        for(String name : this.bonePartMap.getNames()){
+            List<BoneMotion> motionList =
+                    this.bonePartMap.getNamedList(name);
+            Collections.sort(motionList, FrameNumbered.COMPARATOR);
+        }
+
+        for(String name : this.morphPartMap.getNames()){
+            List<MorphMotion> motionList =
+                    this.morphPartMap.getNamedList(name);
+            Collections.sort(motionList, FrameNumbered.COMPARATOR);
+        }
+
+        Collections.sort(this.cameraMotionList,   FrameNumbered.COMPARATOR);
+        Collections.sort(this.luminousMotionList, FrameNumbered.COMPARATOR);
+        Collections.sort(this.shadowMotionList,   FrameNumbered.COMPARATOR);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        String dash = "---";
+
+        StringBuilder result = new StringBuilder();
+
+        result.append("model name : ").append(this.modelName).append('\n');
+
+        for(String boneName : this.bonePartMap.getNames()){
+            List<BoneMotion> motionList =
+                    this.bonePartMap.getNamedList(boneName);
+            for(BoneMotion motion : motionList){
+                result.append(dash).append('\n');
+                result.append(motion.toString()).append('\n');
+            }
+        }
+
+        for(String morphName : this.morphPartMap.getNames()){
+            if(VmdConst.isBaseMorphName(morphName)) continue;
+            List<MorphMotion> motionList =
+                    this.morphPartMap.getNamedList(morphName);
+            for(MorphMotion morph : motionList){
+                result.append(morph.toString()).append('\n');
+            }
+        }
+
+        for(CameraMotion camera : this.cameraMotionList){
+            result.append(dash).append('\n');
+            result.append(camera.toString()).append('\n');
+        }
+
+        for(LuminousMotion luminous : this.luminousMotionList){
+            result.append(luminous.toString()).append('\n');
+        }
+
+        for(ShadowMotion shadow : this.shadowMotionList){
+            result.append(shadow.toString()).append('\n');
+        }
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/BasicExporter.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/BasicExporter.java
new file mode 100644 (file)
index 0000000..5500853
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * bone motion & morph exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+import jp.sourceforge.mikutoga.binio.BinaryExporter;
+import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkQuat;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.BoneMotion;
+import jp.sourceforge.mikutoga.vmd.model.MorphMotion;
+import jp.sourceforge.mikutoga.vmd.model.NamedListMap;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+
+/**
+ * ボーンモーション及びモーフ情報のエクスポーター。
+ */
+class BasicExporter extends BinaryExporter {
+
+    private static final int BZ_SIZE = 4;           // 4byte Bezier parameter
+    private static final int BZXYZR_SIZE = BZ_SIZE * 4; // XYZR Bezier
+    private static final int BZ_REDUNDANT = 4;          // redundant spare
+    private static final int BZTOTAL_SIZE = BZXYZR_SIZE * BZ_REDUNDANT;
+
+    private static final byte[] FDFILLER =
+        { (byte)0x00, (byte)0xfd };
+
+
+    private final byte[] motionIntplt = new byte[BZTOTAL_SIZE];
+
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     */
+    BasicExporter(OutputStream stream){
+        super(stream);
+        return;
+    }
+
+
+    /**
+     * ボーンモーション情報を出力する。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalTextExportException 不正なボーン名の出現
+     */
+    void dumpBoneMotion(VmdMotion motion)
+            throws IOException, IllegalTextExportException{
+        NamedListMap<BoneMotion> map = motion.getBonePartMap();
+        List<String> nameList = map.getNames();
+
+        List<BoneMotion> bmotionList = new LinkedList<BoneMotion>();
+
+        int count = 0;
+        for(String name : nameList){
+            List<BoneMotion> namedList = map.getNamedList(name);
+            for(BoneMotion boneMotion : namedList){
+                bmotionList.add(boneMotion);
+                count++;
+            }
+        }
+        dumpInt(count);
+
+        for(BoneMotion boneMotion : bmotionList){
+            String boneName = boneMotion.getBoneName();
+            dumpFixedW31j(boneName, VmdConst.BONENAME_MAX, FDFILLER);
+
+            int frame = boneMotion.getFrameNumber();
+            dumpInt(frame);
+
+            MkPos3D position = boneMotion.getPosition();
+            dumpBonePosition(position);
+
+            MkQuat rotation = boneMotion.getRotation();
+            dumpBoneRotation(rotation);
+
+            dumpBoneInterpolation(boneMotion);
+        }
+
+        return;
+    }
+
+    /**
+     * ボーン位置情報を出力する。
+     * @param position ボーン位置情報
+     * @throws IOException 出力エラー
+     */
+    private void dumpBonePosition(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;
+    }
+
+    /**
+     * ボーン回転情報を出力する。
+     * @param rotation ボーン回転情報
+     * @throws IOException 出力エラー
+     */
+    private void dumpBoneRotation(MkQuat rotation)
+            throws IOException{
+        float qx = (float) rotation.getQ1();
+        float qy = (float) rotation.getQ2();
+        float qz = (float) rotation.getQ3();
+        float qw = (float) rotation.getQW();
+
+        dumpFloat(qx);
+        dumpFloat(qy);
+        dumpFloat(qz);
+        dumpFloat(qw);
+
+        return;
+    }
+
+    /**
+     * ボーンモーションの補間情報を出力する。
+     * @param boneMotion ボーンモーション
+     * @throws IOException 出力エラー
+     */
+    private void dumpBoneInterpolation(BoneMotion boneMotion)
+            throws IOException{
+        PosCurve posCurve = boneMotion.getPosCurve();
+        BezierParam xCurve = posCurve.getIntpltXpos();
+        BezierParam yCurve = posCurve.getIntpltYpos();
+        BezierParam zCurve = posCurve.getIntpltZpos();
+        BezierParam rCurve = boneMotion.getIntpltRotation();
+
+        int idx = 0;
+
+        this.motionIntplt[idx++] = xCurve.getP1x();
+        this.motionIntplt[idx++] = yCurve.getP1x();
+        this.motionIntplt[idx++] = zCurve.getP1x();
+        this.motionIntplt[idx++] = rCurve.getP1x();
+
+        this.motionIntplt[idx++] = xCurve.getP1y();
+        this.motionIntplt[idx++] = yCurve.getP1y();
+        this.motionIntplt[idx++] = zCurve.getP1y();
+        this.motionIntplt[idx++] = rCurve.getP1y();
+
+        this.motionIntplt[idx++] = xCurve.getP2x();
+        this.motionIntplt[idx++] = yCurve.getP2x();
+        this.motionIntplt[idx++] = zCurve.getP2x();
+        this.motionIntplt[idx++] = rCurve.getP2x();
+
+        this.motionIntplt[idx++] = xCurve.getP2y();
+        this.motionIntplt[idx++] = yCurve.getP2y();
+        this.motionIntplt[idx++] = zCurve.getP2y();
+        this.motionIntplt[idx++] = rCurve.getP2y();
+
+        assert idx == BZXYZR_SIZE;
+
+        redundantCopy();
+
+        dumpByteArray(this.motionIntplt);
+
+        return;
+    }
+
+    /**
+     * 補間情報冗長部の組み立て。
+     */
+    private void redundantCopy(){
+        int lack = 1;
+        for(int ct = 1; ct < BZ_REDUNDANT; ct++){
+            int sourceIdx = 0 + lack;
+            int targetIdx = BZXYZR_SIZE * ct;
+            int span = BZXYZR_SIZE - lack;
+
+            System.arraycopy(this.motionIntplt, sourceIdx,
+                             this.motionIntplt, targetIdx,
+                             span );
+
+            int onePos = targetIdx + span;
+            this.motionIntplt[onePos] = (byte) 0x01;
+
+            int zeroPosStart = onePos + 1;
+            int zeroPosEnd = targetIdx + BZXYZR_SIZE;
+            for(int idx = zeroPosStart; idx < zeroPosEnd; idx++){
+                this.motionIntplt[idx] = (byte) 0x00;
+            }
+
+            lack++;
+        }
+
+        return;
+    }
+
+    /**
+     * モーフ情報を出力する。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalTextExportException 不正なモーフ名の出現
+     */
+    void dumpMorphMotion(VmdMotion motion)
+            throws IOException, IllegalTextExportException{
+        NamedListMap<MorphMotion> map = motion.getMorphPartMap();
+        List<String> nameList = map.getNames();
+
+        List<MorphMotion> morphList = new LinkedList<MorphMotion>();
+
+        int count = 0;
+        for(String name : nameList){
+            List<MorphMotion> namedList = map.getNamedList(name);
+            for(MorphMotion morphMotion : namedList){
+                morphList.add(morphMotion);
+                count++;
+            }
+        }
+        dumpInt(count);
+
+        for(MorphMotion morphMotion : morphList){
+            String morphName = morphMotion.getMorphName();
+            dumpFixedW31j(morphName, VmdConst.MORPHNAME_MAX, FDFILLER);
+
+            int frame = morphMotion.getFrameNumber();
+            dumpInt(frame);
+
+            float flex = morphMotion.getFlex();
+            dumpFloat(flex);
+        }
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/BasicLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/BasicLoader.java
new file mode 100644 (file)
index 0000000..d7f6114
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * basic information builder
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkQuat;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.BoneMotion;
+import jp.sourceforge.mikutoga.vmd.model.MorphMotion;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.vmd.parser.VmdBasicHandler;
+
+/**
+ * ボーンモーション、モーフ情報のビルダ。
+ */
+class BasicLoader implements VmdBasicHandler {
+
+    private final VmdMotion vmdMotion;
+
+    private BoneMotion currentBoneMotion;
+    private MorphMotion currentMorphMotion;
+
+    private boolean hasMoreData;
+
+
+    /**
+     * コンストラクタ。
+     * @param vmdMotion モーション情報
+     */
+    BasicLoader(VmdMotion vmdMotion){
+        super();
+        this.vmdMotion = vmdMotion;
+
+        this.currentBoneMotion  = null;
+        this.currentMorphMotion = null;
+
+        this.hasMoreData = false;
+
+        return;
+    }
+
+
+    /**
+     * ボーンモーションループか否か判定する。
+     * @param stage 判定対象
+     * @return モーションループならtrue
+     */
+    private static boolean isBoneMotionList(ParseStage stage){
+        if(stage == VmdBasicHandler.BONEMOTION_LIST) return true;
+        return false;
+    }
+
+    /**
+     * モーフループか否か判定する。
+     * @param stage 判定対象
+     * @return モーフループならtrue
+     */
+    private static boolean isMorphList(ParseStage stage){
+        if(stage == VmdBasicHandler.MORPH_LIST) return true;
+        return false;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdParseStart() throws MmdFormatException{
+        // NOTHING
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param hasMoreDataArg {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdParseEnd(boolean hasMoreDataArg)
+            throws MmdFormatException{
+        this.hasMoreData = hasMoreDataArg;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops)
+            throws MmdFormatException{
+        if(isBoneMotionList(stage)){
+            this.currentBoneMotion = new BoneMotion();
+        }else if(isMorphList(stage)){
+            this.currentMorphMotion = new MorphMotion();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage)
+            throws MmdFormatException{
+        if(isBoneMotionList(stage)){
+            this.vmdMotion.addBoneMotion(this.currentBoneMotion);
+            this.currentBoneMotion = new BoneMotion();
+        }else if(isMorphList(stage)){
+            this.vmdMotion.addMorphMotion(this.currentMorphMotion);
+            this.currentMorphMotion = new MorphMotion();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage)
+            throws MmdFormatException{
+        if(isBoneMotionList(stage)){
+            this.currentBoneMotion = null;
+        }else if(isMorphList(stage)){
+            this.currentMorphMotion = null;
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param header {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdHeaderInfo(byte[] header) throws MmdFormatException{
+        // NOTHING
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param modelName {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdModelName(String modelName) throws MmdFormatException{
+        this.vmdMotion.setModelName(modelName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param boneName {@inheritDoc}
+     * @param keyFrameNo {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBoneMotion(String boneName, int keyFrameNo)
+            throws MmdFormatException{
+        this.currentBoneMotion.setBoneName(boneName);
+        this.currentBoneMotion.setFrameNumber(keyFrameNo);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param qx {@inheritDoc}
+     * @param qy {@inheritDoc}
+     * @param qz {@inheritDoc}
+     * @param qw {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBoneRotationQt(float qx, float qy, float qz, float qw)
+            throws MmdFormatException{
+        MkQuat rotation = this.currentBoneMotion.getRotation();
+        rotation.setQ1(qx);
+        rotation.setQ2(qy);
+        rotation.setQ3(qz);
+        rotation.setQW(qw);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xPos {@inheritDoc}
+     * @param yPos {@inheritDoc}
+     * @param zPos {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBonePosition(float xPos, float yPos, float zPos)
+            throws MmdFormatException{
+        MkPos3D position = this.currentBoneMotion.getPosition();
+        position.setPosition(xPos, yPos, zPos);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rP1x {@inheritDoc}
+     * @param rP1y {@inheritDoc}
+     * @param rP2x {@inheritDoc}
+     * @param rP2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBoneIntpltRot(byte rP1x, byte rP1y,
+                                      byte rP2x, byte rP2y )
+            throws MmdFormatException{
+        BezierParam bezier = this.currentBoneMotion.getIntpltRotation();
+        bezier.setP1(rP1x, rP1y);
+        bezier.setP2(rP2x, rP2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xP1x {@inheritDoc}
+     * @param xP1y {@inheritDoc}
+     * @param xP2x {@inheritDoc}
+     * @param xP2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBoneIntpltXpos(byte xP1x, byte xP1y,
+                                       byte xP2x, byte xP2y )
+            throws MmdFormatException{
+        PosCurve posCurve = this.currentBoneMotion.getPosCurve();
+        BezierParam bezier = posCurve.getIntpltXpos();
+        bezier.setP1(xP1x, xP1y);
+        bezier.setP2(xP2x, xP2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param yP1x {@inheritDoc}
+     * @param yP1y {@inheritDoc}
+     * @param yP2x {@inheritDoc}
+     * @param yP2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBoneIntpltYpos(byte yP1x, byte yP1y,
+                                       byte yP2x, byte yP2y )
+            throws MmdFormatException{
+        PosCurve posCurve = this.currentBoneMotion.getPosCurve();
+        BezierParam bezier = posCurve.getIntpltYpos();
+        bezier.setP1(yP1x, yP1y);
+        bezier.setP2(yP2x, yP2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param zP1x {@inheritDoc}
+     * @param zP1y {@inheritDoc}
+     * @param zP2x {@inheritDoc}
+     * @param zP2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdBoneIntpltZpos(byte zP1x, byte zP1y,
+                                       byte zP2x, byte zP2y )
+            throws MmdFormatException{
+        PosCurve posCurve = this.currentBoneMotion.getPosCurve();
+        BezierParam bezier = posCurve.getIntpltZpos();
+        bezier.setP1(zP1x, zP1y);
+        bezier.setP2(zP2x, zP2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param morphName {@inheritDoc}
+     * @param keyFrameNo {@inheritDoc}
+     * @param flex {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdMorphMotion(String morphName, int keyFrameNo, float flex)
+            throws MmdFormatException{
+        this.currentMorphMotion.setMorphName(morphName);
+        this.currentMorphMotion.setFrameNumber(keyFrameNo);
+        this.currentMorphMotion.setFlex(flex);
+        return;
+    }
+
+    /**
+     * 読み残したデータがあるか判定する。
+     * @return 読み残したデータがあればtrue
+     */
+    boolean hasMoreData(){
+        return this.hasMoreData;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/CameraExporter.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/CameraExporter.java
new file mode 100644 (file)
index 0000000..01fbec1
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * camera information exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import jp.sourceforge.mikutoga.binio.BinaryExporter;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.CameraMotion;
+import jp.sourceforge.mikutoga.vmd.model.CameraRotation;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+
+/**
+ * カメラ情報のエクスポーター。
+ */
+class CameraExporter extends BinaryExporter {
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     */
+    CameraExporter(OutputStream stream){
+        super(stream);
+        return;
+    }
+
+
+    /**
+     * カメラモーション情報を出力する。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    void dumpCameraMotion(VmdMotion motion) throws IOException{
+        List<CameraMotion> list = motion.getCameraMotionList();
+
+        int count = list.size();
+        dumpInt(count);
+
+        for(CameraMotion cameraMotion : list){
+            int frame = cameraMotion.getFrameNumber();
+            dumpInt(frame);
+
+            float range = cameraMotion.getRange();
+            dumpFloat(range);
+
+            MkPos3D targetPos = cameraMotion.getCameraTarget();
+            dumpFloat((float) targetPos.getXpos());
+            dumpFloat((float) targetPos.getYpos());
+            dumpFloat((float) targetPos.getZpos());
+
+            CameraRotation rotation = cameraMotion.getCameraRotation();
+            dumpFloat(rotation.getLatitude());
+            dumpFloat(rotation.getLongitude());
+            dumpFloat(rotation.getRoll());
+
+            dumpCameraCurve(cameraMotion);
+
+            dumpInt(cameraMotion.getProjectionAngle());
+
+            byte perspectiveSwitch;
+            if(cameraMotion.hasPerspective()) perspectiveSwitch = 0x00;
+            else                              perspectiveSwitch = 0x01;
+            dumpByte(perspectiveSwitch);
+        }
+
+        return;
+    }
+
+    /**
+     * カメラ補間情報を出力する。
+     * @param cameraMotion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    private void dumpCameraCurve(CameraMotion cameraMotion)
+            throws IOException{
+        PosCurve posCurve = cameraMotion.getTargetPosCurve();
+        BezierParam xCurve = posCurve.getIntpltXpos();
+        BezierParam yCurve = posCurve.getIntpltYpos();
+        BezierParam zCurve = posCurve.getIntpltZpos();
+        dumpCameraBezier(xCurve);
+        dumpCameraBezier(yCurve);
+        dumpCameraBezier(zCurve);
+
+        BezierParam rotCurve   = cameraMotion.getIntpltRotation();
+        BezierParam rangeCurve = cameraMotion.getIntpltRange();
+        BezierParam projCurve  = cameraMotion.getIntpltProjection();
+        dumpCameraBezier(rotCurve);
+        dumpCameraBezier(rangeCurve);
+        dumpCameraBezier(projCurve);
+
+        return;
+    }
+
+    /**
+     * ベジェ曲線情報を出力する。
+     * @param bezier ベジェ曲線
+     * @throws IOException 出力エラー
+     */
+    private void dumpCameraBezier(BezierParam bezier) throws IOException{
+        dumpByte(bezier.getP1x());
+        dumpByte(bezier.getP2x());
+        dumpByte(bezier.getP1y());
+        dumpByte(bezier.getP2y());
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/CameraLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/CameraLoader.java
new file mode 100644 (file)
index 0000000..1193af2
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * camera information builder
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.util.List;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.CameraMotion;
+import jp.sourceforge.mikutoga.vmd.model.CameraRotation;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.vmd.parser.VmdCameraHandler;
+
+/**
+ * カメラ情報のビルダ。
+ */
+class CameraLoader implements VmdCameraHandler {
+
+    private final List<CameraMotion> cameraMotionList;
+
+    private CameraMotion currentCameraMotion;
+
+
+    /**
+     * コンストラクタ。
+     * @param vmdMotion モーションデータ。
+     */
+    CameraLoader(VmdMotion vmdMotion){
+        super();
+        this.cameraMotionList = vmdMotion.getCameraMotionList();
+        this.currentCameraMotion = null;
+        return;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops)
+            throws MmdFormatException{
+        this.currentCameraMotion = new CameraMotion();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage)
+            throws MmdFormatException{
+        this.cameraMotionList.add(this.currentCameraMotion);
+        this.currentCameraMotion = new CameraMotion();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage)
+            throws MmdFormatException{
+        this.currentCameraMotion = null;
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param keyFrameNo {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraMotion(int keyFrameNo) throws MmdFormatException{
+        this.currentCameraMotion.setFrameNumber(keyFrameNo);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xPos {@inheritDoc}
+     * @param yPos {@inheritDoc}
+     * @param zPos {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraPosition(float xPos, float yPos, float zPos)
+            throws MmdFormatException{
+        this.currentCameraMotion.getCameraTarget()
+                .setPosition(xPos, yPos, zPos);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param latitude {@inheritDoc}
+     * @param longitude {@inheritDoc}
+     * @param roll {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraRotation(float latitude,
+                                    float longitude,
+                                    float roll )
+            throws MmdFormatException{
+        CameraRotation rotation =
+                this.currentCameraMotion.getCameraRotation();
+        rotation.setLatitude(latitude);
+        rotation.setLongitude(longitude);
+        rotation.setRoll(roll);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param range {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraRange(float range) throws MmdFormatException{
+        this.currentCameraMotion.setRange(range);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param angle {@inheritDoc}
+     * @param hasPerspective {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraProjection(int angle, boolean hasPerspective)
+            throws MmdFormatException{
+        this.currentCameraMotion.setProjectionAngle(angle);
+        this.currentCameraMotion.setPerspectiveMode(hasPerspective);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param p1x {@inheritDoc}
+     * @param p1y {@inheritDoc}
+     * @param p2x {@inheritDoc}
+     * @param p2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraIntpltXpos(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException{
+        PosCurve posCurve = this.currentCameraMotion.getTargetPosCurve();
+        BezierParam bezier = posCurve.getIntpltXpos();
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param p1x {@inheritDoc}
+     * @param p1y {@inheritDoc}
+     * @param p2x {@inheritDoc}
+     * @param p2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraIntpltYpos(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException{
+        PosCurve posCurve = this.currentCameraMotion.getTargetPosCurve();
+        BezierParam bezier = posCurve.getIntpltYpos();
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param p1x {@inheritDoc}
+     * @param p1y {@inheritDoc}
+     * @param p2x {@inheritDoc}
+     * @param p2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraIntpltZpos(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException{
+        PosCurve posCurve = this.currentCameraMotion.getTargetPosCurve();
+        BezierParam bezier = posCurve.getIntpltZpos();
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param p1x {@inheritDoc}
+     * @param p1y {@inheritDoc}
+     * @param p2x {@inheritDoc}
+     * @param p2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraIntpltRotation(byte p1x, byte p1y,
+                                           byte p2x, byte p2y )
+            throws MmdFormatException{
+        BezierParam bezier = this.currentCameraMotion.getIntpltRotation();
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param p1x {@inheritDoc}
+     * @param p1y {@inheritDoc}
+     * @param p2x {@inheritDoc}
+     * @param p2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraIntpltRange(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException{
+        BezierParam bezier = this.currentCameraMotion.getIntpltRange();
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param p1x {@inheritDoc}
+     * @param p1y {@inheritDoc}
+     * @param p2x {@inheritDoc}
+     * @param p2y {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdCameraIntpltProjection(byte p1x, byte p1y,
+                                              byte p2x, byte p2y )
+            throws MmdFormatException{
+        BezierParam bezier = this.currentCameraMotion.getIntpltProjection();
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/LightingExporter.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/LightingExporter.java
new file mode 100644 (file)
index 0000000..cd67ac2
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * lighting information exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import jp.sourceforge.mikutoga.binio.BinaryExporter;
+import jp.sourceforge.mikutoga.vmd.model.LuminousColor;
+import jp.sourceforge.mikutoga.vmd.model.LuminousMotion;
+import jp.sourceforge.mikutoga.vmd.model.LuminousVector;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMode;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMotion;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+
+/**
+ * ライティング情報のエクスポーター。
+ */
+class LightingExporter extends BinaryExporter {
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     */
+    LightingExporter(OutputStream stream){
+        super(stream);
+        return;
+    }
+
+
+    /**
+     * 照明情報を出力する。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    void dumpLuminousMotion(VmdMotion motion) throws IOException{
+        List<LuminousMotion> list = motion.getLuminousMotionList();
+
+        int count = list.size();
+        dumpInt(count);
+
+        for(LuminousMotion luminousMotion : list){
+            int frame = luminousMotion.getFrameNumber();
+            dumpInt(frame);
+
+            LuminousColor color = luminousMotion.getColor();
+            dumpFloat(color.getColR());
+            dumpFloat(color.getColG());
+            dumpFloat(color.getColB());
+
+            LuminousVector vector = luminousMotion.getDirection();
+            dumpFloat(vector.getVecX());
+            dumpFloat(vector.getVecY());
+            dumpFloat(vector.getVecZ());
+        }
+
+        return;
+    }
+
+    /**
+     * シャドウ演出情報を出力する。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    void dumpShadowMotion(VmdMotion motion) throws IOException{
+        List<ShadowMotion> list = motion.getShadowMotionList();
+
+        int count = list.size();
+        dumpInt(count);
+
+        for(ShadowMotion shadowMotion : list){
+            int frame = shadowMotion.getFrameNumber();
+            dumpInt(frame);
+
+            ShadowMode mode = shadowMotion.getShadowMode();
+            byte shadowType = mode.getEncodedByte();
+            dumpByte(shadowType);
+
+            float rawParam = shadowMotion.getRawScopeParam();
+            dumpFloat(rawParam);
+        }
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/LightingLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/LightingLoader.java
new file mode 100644 (file)
index 0000000..01e0dd9
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * lighting information builder
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.util.List;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+import jp.sourceforge.mikutoga.vmd.model.LuminousColor;
+import jp.sourceforge.mikutoga.vmd.model.LuminousMotion;
+import jp.sourceforge.mikutoga.vmd.model.LuminousVector;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMode;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMotion;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.vmd.parser.VmdLightingHandler;
+
+/**
+ * ライティング情報のビルダ。
+ */
+class LightingLoader implements VmdLightingHandler {
+
+    private final List<LuminousMotion> luminousMotionList;
+    private final List<ShadowMotion> shadowMotionList;
+
+    private LuminousMotion currentLuminousMotion;
+    private ShadowMotion currentShadowMotion;
+
+
+    /**
+     * コンストラクタ。
+     * @param vmdMotion モーションデータ。
+     */
+    LightingLoader(VmdMotion vmdMotion){
+        super();
+
+        this.luminousMotionList = vmdMotion.getLuminousMotionList();
+        this.shadowMotionList   = vmdMotion.getShadowMotionList();
+
+        this.currentLuminousMotion = null;
+        this.currentShadowMotion = null;
+
+        return;
+    }
+
+
+    /**
+     * 照明ループか否か判定する。
+     * @param stage 判定対象
+     * @return 照明ループならtrue
+     */
+    private static boolean isLuminousList(ParseStage stage){
+        if(stage == VmdLightingHandler.LUMINOUS_LIST) return true;
+        return false;
+    }
+
+    /**
+     * シャドウループか否か判定する。
+     * @param stage 判定対象
+     * @return シャドウループならtrue
+     */
+    private static boolean isShadowList(ParseStage stage){
+        if(stage == VmdLightingHandler.SHADOW_LIST) return true;
+        return false;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @param loops {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopStart(ParseStage stage, int loops)
+            throws MmdFormatException{
+        if(isLuminousList(stage)){
+            this.currentLuminousMotion = new LuminousMotion();
+        }else if(isShadowList(stage)){
+            this.currentShadowMotion = new ShadowMotion();
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopNext(ParseStage stage)
+            throws MmdFormatException{
+        if(isLuminousList(stage)){
+            this.luminousMotionList.add(this.currentLuminousMotion);
+            this.currentLuminousMotion = new LuminousMotion();
+        }else if(isShadowList(stage)){
+            this.shadowMotionList.add(this.currentShadowMotion);
+            this.currentShadowMotion = new ShadowMotion();
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param stage {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void loopEnd(ParseStage stage)
+            throws MmdFormatException{
+        if(isLuminousList(stage)){
+            this.currentLuminousMotion = null;
+        }else if(isShadowList(stage)){
+            this.currentShadowMotion = null;
+        }
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param keyFrameNo {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdLuminousMotion(int keyFrameNo) throws MmdFormatException{
+        this.currentLuminousMotion.setFrameNumber(keyFrameNo);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param rVal {@inheritDoc}
+     * @param gVal {@inheritDoc}
+     * @param bVal {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdLuminousColor(float rVal, float gVal, float bVal)
+            throws MmdFormatException{
+        LuminousColor color = this.currentLuminousMotion.getColor();
+        color.setColR(rVal);
+        color.setColG(gVal);
+        color.setColB(bVal);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param xVec {@inheritDoc}
+     * @param yVec {@inheritDoc}
+     * @param zVec {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdLuminousDirection(float xVec, float yVec, float zVec)
+            throws MmdFormatException{
+        LuminousVector direction = this.currentLuminousMotion.getDirection();
+        direction.setVecX(xVec);
+        direction.setVecY(yVec);
+        direction.setVecZ(zVec);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param keyFrameNo {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdShadowMotion(int keyFrameNo) throws MmdFormatException{
+        this.currentShadowMotion.setFrameNumber(keyFrameNo);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param shadowMode {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdShadowMode(byte shadowMode) throws MmdFormatException{
+        ShadowMode mode = ShadowMode.decode(shadowMode);
+        this.currentShadowMotion.setShadowMode(mode);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param shadowScope {@inheritDoc}
+     * @throws MmdFormatException {@inheritDoc}
+     */
+    @Override
+    public void vmdShadowScopeRaw(float shadowScope)
+            throws MmdFormatException{
+        this.currentShadowMotion.setRawScopeParam(shadowScope);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/VmdExporter.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/VmdExporter.java
new file mode 100644 (file)
index 0000000..a27ee9d
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * vmd exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import jp.sourceforge.mikutoga.binio.BinaryExporter;
+import jp.sourceforge.mikutoga.binio.IllegalTextExportException;
+import jp.sourceforge.mikutoga.vmd.IllegalVmdDataException;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+
+/**
+ * VMDファイルのエクスポーター。
+ */
+public class VmdExporter extends BinaryExporter {
+
+    private static final byte[] HEADFILLER = {
+        (byte)'J',
+        (byte)'K',
+        (byte)'L',
+        (byte)'M',
+    };
+
+    private static final byte[] FDFILLER =
+        { (byte)0x00, (byte)0xfd };
+
+
+    private final BasicExporter    basicExporter;
+    private final CameraExporter   cameraExporter;
+    private final LightingExporter lightingExporter;
+
+
+    /**
+     * コンストラクタ。
+     * @param stream 出力ストリーム
+     */
+    public VmdExporter(OutputStream stream){
+        super(stream);
+
+        this.basicExporter    = new BasicExporter(stream);
+        this.cameraExporter   = new CameraExporter(stream);
+        this.lightingExporter = new LightingExporter(stream);
+
+        return;
+    }
+
+    /**
+     * モーションデータをVMDファイル形式で出力する。
+     * <p>異常時には出力データのフラッシュが試みられる。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalVmdDataException モーションデータに不備が発見された
+     */
+    public void dumpVmdMotion(VmdMotion motion)
+            throws IOException, IllegalVmdDataException{
+        try{
+            dumpVmdMotionImpl(motion);
+        }finally{
+            flush();
+        }
+
+        return;
+    }
+
+    /**
+     * モーションデータをVMDファイル形式で出力する。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalVmdDataException モーションデータに不備が発見された
+     */
+    private void dumpVmdMotionImpl(VmdMotion motion)
+            throws IOException, IllegalVmdDataException{
+        dumpHeader();
+
+        try{
+            dumpModelName(motion);
+            this.basicExporter.dumpBoneMotion(motion);
+            this.basicExporter.dumpMorphMotion(motion);
+        }catch(IllegalTextExportException e){
+            throw new IllegalVmdDataException(e);
+        }
+
+        this.cameraExporter.dumpCameraMotion(motion);
+        this.lightingExporter.dumpLuminousMotion(motion);
+        this.lightingExporter.dumpShadowMotion(motion);
+
+        return;
+    }
+
+    /**
+     * ヘッダ情報を出力する。
+     * @throws IOException 出力エラー
+     */
+    private void dumpHeader() throws IOException{
+        byte[] header = VmdConst.createMagicHeader();
+        dumpByteArray(header);
+        dumpByteArray(HEADFILLER);
+
+        assert header.length + HEADFILLER.length == VmdConst.HEADER_LENGTH;
+
+        return;
+    }
+
+    /**
+     * モデル名を出力する。
+     * <p>演出データのモデル名には
+     * 便宜的に{@link VmdConst.MODELNAME_STAGEACT}が使われる。
+     * @param motion モーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalTextExportException 不正なモデル名の出現
+     */
+    private void dumpModelName(VmdMotion motion)
+            throws IOException, IllegalTextExportException{
+        String modelName = motion.getModelName();
+        if(modelName == null) modelName = VmdConst.MODELNAME_STAGEACT;
+
+        dumpFixedW31j(modelName, VmdConst.MODELNAME_MAX, FDFILLER);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/VmdLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/VmdLoader.java
new file mode 100644 (file)
index 0000000..aa92058
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * VMD loader
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+import java.io.IOException;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.MmdSource;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.vmd.parser.VmdParser;
+
+/**
+ * VMDモーションファイルを読み込むためのバイナリローダ。
+ */
+public final class VmdLoader {
+
+    /**
+     * コンストラクタ。
+     */
+    private VmdLoader(){
+        super();
+        assert false;
+        throw new AssertionError();
+    }
+
+
+    /**
+     * VMDファイルの読み込みを行いモーション情報を返す。
+     * @param source 入力ソース
+     * @return モーション情報
+     * @throws IOException 入力エラー
+     * @throws MmdFormatException VMDファイルフォーマットの異常を検出
+     */
+    public static VmdMotion load(MmdSource source)
+            throws IOException,
+                   MmdFormatException {
+        VmdParser parser = new VmdParser(source);
+
+        VmdMotion result = new VmdMotion();
+
+        BasicLoader basicBuilder       = new BasicLoader(result);
+        CameraLoader cameraBuilder     = new CameraLoader(result);
+        LightingLoader lightingBuilder = new LightingLoader(result);
+
+        parser.setBasicHandler(basicBuilder);
+        parser.setCameraHandler(cameraBuilder);
+        parser.setLightingHandler(lightingBuilder);
+
+        parser.parseVmd();
+
+        return result;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/package-info.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/binio/package-info.java
new file mode 100644 (file)
index 0000000..616da2a
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+/**
+ * 独自データモデル向けVMDモーションファイル(*.vmd)用入出力ライブラリ。
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.binio;
+
+/* EOF */
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/package-info.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/package-info.java
new file mode 100644 (file)
index 0000000..c8ae7fa
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+/**
+ * VMDモーションファイル内の各要素に相当するオブジェクト各種。
+ * <p>VMDを正確に記述するのが主目的。使い勝手は二の次。
+ */
+
+package jp.sourceforge.mikutoga.vmd.model;
+
+/* EOF */
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/VmdXmlExporter.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/VmdXmlExporter.java
new file mode 100644 (file)
index 0000000..e1fcf04
--- /dev/null
@@ -0,0 +1,800 @@
+/*
+ * vmd-xml exporter
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import jp.sourceforge.mikutoga.math.EulerYXZ;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkQuat;
+import jp.sourceforge.mikutoga.typical.TypicalBone;
+import jp.sourceforge.mikutoga.typical.TypicalMorph;
+import jp.sourceforge.mikutoga.vmd.IllegalVmdDataException;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.BoneMotion;
+import jp.sourceforge.mikutoga.vmd.model.CameraMotion;
+import jp.sourceforge.mikutoga.vmd.model.CameraRotation;
+import jp.sourceforge.mikutoga.vmd.model.LuminousColor;
+import jp.sourceforge.mikutoga.vmd.model.LuminousMotion;
+import jp.sourceforge.mikutoga.vmd.model.LuminousVector;
+import jp.sourceforge.mikutoga.vmd.model.MorphMotion;
+import jp.sourceforge.mikutoga.vmd.model.NamedListMap;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMode;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMotion;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.xml.BasicXmlExporter;
+import jp.sourceforge.mikutoga.xml.XmlResourceResolver;
+
+/**
+ * VMDモーションデータをXMLへエクスポートする。
+ */
+public class VmdXmlExporter extends BasicXmlExporter {
+
+    private static final String XSINS = "xsi";
+
+    private static final String TOP_COMMENT =
+            "  MikuMikuDance\n    motion-data(*.vmd) on XML";
+
+    private static final String QUATERNION_COMMENT =
+          "  bone-rotation has Quaternion parameters [boneRotQuat]\n"
+        + "  or YXZ-Euler angles [boneRotEyxz].\n"
+        + "  Quaternion is strongly recommended"
+        +   " if you are data-exchanging.";
+
+    private static final String BEZIER_COMMENT =
+          "  motion interpolation is defined by Bezier-cubic-curve.\n"
+        + "  implicit bezier curve point : P0=(0,0) P3=(127,127)\n"
+        + "  defLinear : MMD default linear curve."
+        + " P1=(20,20) P2=(107,107) [DEFAULT]\n"
+        + "  defEaseInOut : MMD default ease-in-out curve."
+        + " P1=(64,0) P2=(64,127)";
+
+    private static final String CAMERA_COMMENT =
+          "  camera-rotation has polar-coordinates parameters.\n"
+        + "  xRad = -radian(UI_X) [latitude]\n"
+        + "  yRad =  radian(UI_Y) [longitude]\n"
+        + "  zRad =  radian(UI_Z) [roll]\n"
+        + "  range = -(UI_RANGE)";
+
+    private static final String SHADOW_COMMENT =
+             "  UI_VALUE = EFFECTIVE_RANGE * 100 ???\n"
+            +"  rawParam = 0.1 - (UI_VALUE / 1.0E+5)\n\n"
+            +"  NONE   : no self-shadow\n"
+            +"  MODE_1 : reduce shadow-quality suddenly at range\n"
+            +"  MODE_2 : reduce shadow-quality gradually with range";
+
+
+    private boolean isQuaternionMode = true;
+    private String generator = "";
+
+
+    /**
+     * コンストラクタ。
+     * 文字エンコーディングはUTF-8が用いられる。
+     * @param stream 出力ストリーム
+     */
+    public VmdXmlExporter(OutputStream stream){
+        super(stream);
+        return;
+    }
+
+
+    /**
+     * ボーン回転量をクォータニオンで出力するか否か設定する。
+     * <p>デフォルトではtrue
+     * @param mode trueだとクォータニオン、falseだとオイラー角で出力される。
+     */
+    public void setQuaternionMode(boolean mode){
+        this.isQuaternionMode = mode;
+    }
+
+    /**
+     * Generatorメタ情報を設定する。
+     * @param generatorArg Generatorメタ情報
+     * @throws NullPointerException 引数がnull
+     */
+    public void setGenerator(String generatorArg)
+            throws NullPointerException{
+        if(generatorArg == null) throw new NullPointerException();
+        this.generator = generatorArg;
+        return;
+    }
+
+    /**
+     * VMDモーションデータをXML形式で出力する。
+     * <p>モーションデータと演出データで振り分けられる。
+     * @param vmdMotion VMDモーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalVmdDataException 不正なモーションデータを検出
+     */
+    public void putVmdXml(VmdMotion vmdMotion)
+            throws IOException, IllegalVmdDataException{
+        try{
+            putVmdXmlImpl(vmdMotion);
+        }finally{
+            flush();
+        }
+        return;
+    }
+
+    /**
+     * VMDモーションデータをXML形式で出力する。
+     * <p>モーションデータと演出データで振り分けられる。
+     * @param vmdMotion VMDモーションデータ
+     * @throws IOException 出力エラー
+     * @throws IllegalVmdDataException 不正なモーションデータを検出
+     */
+    private void putVmdXmlImpl(VmdMotion vmdMotion)
+            throws IOException, IllegalVmdDataException{
+        ind().put("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>").ln(2);
+
+        ind().putBlockComment(TOP_COMMENT).ln(2);
+
+        ind().put("<vmdMotion").ln();
+        pushNest();
+        ind().putAttr("xmlns", VmdXmlResources.NS_VMDXML).ln();
+        ind().putAttr("xmlns:" + XSINS, XmlResourceResolver.NS_XSD).ln();
+
+        ind().put(XSINS).put(":schemaLocation=").put('"');
+        put(VmdXmlResources.NS_VMDXML).ln();
+        ind().sp(2).put(VmdXmlResources.SCHEMAURI_VMDXML).put('"').ln();
+
+        ind().putAttr("version", VmdXmlResources.VER_VMDXML).ln();
+        popNest();
+        put(">").ln(2);
+
+        if(this.generator != null && this.generator.length() > 0){
+            ind().put("<meta ");
+            putAttr("name", "generator").put(' ');
+            putAttr("content", this.generator);
+            put(" />").ln(2);
+        }
+
+        if(vmdMotion.isModelMotion()){
+            putModelName(vmdMotion);
+            putBoneMotionSequence(vmdMotion);
+            putMorphSequence(vmdMotion);
+        }else{
+            putCameraSequence(vmdMotion);
+            putLuminousSequence(vmdMotion);
+            putShadowSequence(vmdMotion);
+        }
+
+        ind().put("</vmdMotion>").ln(2);
+        ind().put("<!-- EOF -->").ln();
+
+        return;
+    }
+
+    /**
+     * 位置移動補間カーブを出力する。
+     * @param posCurve 移動補間情報
+     * @throws IOException 出力エラー
+     */
+    private void putPositionCurve(PosCurve posCurve)
+            throws IOException{
+        BezierParam xCurve = posCurve.getIntpltXpos();
+        BezierParam yCurve = posCurve.getIntpltYpos();
+        BezierParam zCurve = posCurve.getIntpltZpos();
+
+        ind().putLineComment("X-Y-Z interpolation *3").ln();
+
+        ind();
+        putBezierCurve(xCurve);
+        ln();
+
+        ind();
+        putBezierCurve(yCurve);
+        ln();
+
+        ind();
+        putBezierCurve(zCurve);
+        ln();
+
+        return;
+    }
+
+    /**
+     * ベジェ曲線による補間曲線情報を出力する。
+     * @param bezier ベジェ曲線
+     * @throws IOException 出力エラー
+     */
+    private void putBezierCurve(BezierParam bezier)
+            throws IOException{
+        if(bezier.isDefaultLinear()){
+            put("<defLinear />");
+        }else if(bezier.isDefaultEaseInOut()){
+            put("<defEaseInOut />");
+        }else{
+            put("<bezier ");
+            putIntAttr("p1x", bezier.getP1x()).sp();
+            putIntAttr("p1y", bezier.getP1y()).sp();
+            putIntAttr("p2x", bezier.getP2x()).sp();
+            putIntAttr("p2y", bezier.getP2y()).sp();
+            put("/>");
+        }
+        return;
+    }
+
+    /**
+     * モデル名を出力する。
+     * @param vmdMotion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    private void putModelName(VmdMotion vmdMotion)
+            throws IOException{
+        String modelName = vmdMotion.getModelName();
+
+        ind().putLineComment(modelName).ln();
+        ind().put("<modelName ");
+        putAttr("name", modelName).sp();
+        put("/>").ln(2);
+
+        return;
+    }
+
+    /**
+     * ボーンモーションデータを出力する。
+     * @param vmdMotion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    private void putBoneMotionSequence(VmdMotion vmdMotion)
+            throws IOException{
+        ind().putBlockComment(QUATERNION_COMMENT);
+        ind().putBlockComment(BEZIER_COMMENT);
+
+        ind().put("<boneMotionSequence>").ln();
+
+        pushNest();
+        NamedListMap<BoneMotion> listmap = vmdMotion.getBonePartMap();
+        if( ! listmap.isEmpty() ) ln();
+        for(String boneName : listmap.getNames()){
+            List<BoneMotion> list = listmap.getNamedList(boneName);
+            putBonePart(boneName, list);
+        }
+        popNest();
+
+        ind().put("</boneMotionSequence>").ln(2);
+
+        return;
+    }
+
+    /**
+     * ボーン別モーションデータを出力する。
+     * @param boneName ボーン名
+     * @param list ボーンモーションのリスト
+     * @throws IOException 出力エラー
+     */
+    private void putBonePart(String boneName, List<BoneMotion> list)
+            throws IOException{
+        ind().putLineComment(boneName);
+        String globalName = TypicalBone.primary2global(boneName);
+        if(globalName != null){
+            sp(2).putLineComment("Perhaps : [" + globalName + "]");
+        }
+        ln();
+
+        ind().put("<bonePart ");
+        putAttr("name", boneName).sp();
+        put(">").ln(2);
+
+        pushNest();
+        for(BoneMotion bone : list){
+            putBoneMotion(bone);
+        }
+        popNest();
+
+        ind().put("</bonePart>").ln(2);
+
+        return;
+    }
+
+    /**
+     * ボーンモーションを出力する。
+     * @param boneMotion ボーンモーション
+     * @throws IOException 出力エラー
+     */
+    private void putBoneMotion(BoneMotion boneMotion)
+            throws IOException{
+        ind().put("<boneMotion ");
+        int frameNo = boneMotion.getFrameNumber();
+        putIntAttr("frame", frameNo).sp();
+        put(">").ln();
+
+        pushNest();
+        putBonePosition(boneMotion);
+        if(this.isQuaternionMode){
+            putBoneRotQuat(boneMotion);
+        }else{
+            putBoneRotEyxz(boneMotion);
+        }
+        popNest();
+
+        ind().put("</boneMotion>").ln(2);
+
+        return;
+    }
+
+    /**
+     * ボーン位置を出力する。
+     * @param boneMotion ボーンモーション
+     * @throws IOException 出力エラー
+     */
+    private void putBonePosition(BoneMotion boneMotion)
+            throws IOException{
+        if(boneMotion.hasImplicitPosition()){
+            return;
+        }
+
+        ind().put("<bonePosition ");
+        MkPos3D position = boneMotion.getPosition();
+        float xPos = (float) position.getXpos();
+        float yPos = (float) position.getYpos();
+        float zPos = (float) position.getZpos();
+        putFloatAttr("xPos", xPos).sp();
+        putFloatAttr("yPos", yPos).sp();
+        putFloatAttr("zPos", zPos).sp();
+
+        PosCurve posCurve = boneMotion.getPosCurve();
+        if(posCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+
+            pushNest();
+            putPositionCurve(posCurve);
+            popNest();
+
+            ind().put("</bonePosition>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * ボーン回転を出力する。
+     * @param boneMotion ボーンモーション
+     * @throws IOException 出力エラー
+     */
+    private void putBoneRotQuat(BoneMotion boneMotion)
+            throws IOException{
+        MkQuat rotation = boneMotion.getRotation();
+        BezierParam rotCurve = boneMotion.getIntpltRotation();
+
+        ind().put("<boneRotQuat").ln();
+        pushNest();
+        ind().putFloatAttr("qx", (float) rotation.getQ1()).ln();
+        ind().putFloatAttr("qy", (float) rotation.getQ2()).ln();
+        ind().putFloatAttr("qz", (float) rotation.getQ3()).ln();
+        ind().putFloatAttr("qw", (float) rotation.getQW()).ln();
+        popNest();
+        ind();
+
+        if(rotCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+            pushNest();
+            ind();
+            putBezierCurve(rotCurve);
+            ln();
+            popNest();
+            ind().put("</boneRotQuat>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * ボーン回転を出力する。
+     * @param boneMotion ボーンモーション
+     * @throws IOException 出力エラー
+     */
+    private void putBoneRotEyxz(BoneMotion boneMotion)
+            throws IOException{
+        MkQuat rotation = boneMotion.getRotation();
+        BezierParam rotCurve = boneMotion.getIntpltRotation();
+
+        EulerYXZ euler = new EulerYXZ();
+        rotation.toEulerYXZ(euler);
+        float xDeg = (float)StrictMath.toDegrees(euler.getXRot());
+        float yDeg = (float)StrictMath.toDegrees(euler.getYRot());
+        float zDeg = (float)StrictMath.toDegrees(euler.getZRot());
+
+        ind().put("<boneRotEyxz").ln();
+        pushNest();
+        ind().putFloatAttr("xDeg", xDeg).ln();
+        ind().putFloatAttr("yDeg", yDeg).ln();
+        ind().putFloatAttr("zDeg", zDeg).ln();
+        popNest();
+        ind();
+
+        if(rotCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+            pushNest();
+            ind();
+            putBezierCurve(rotCurve);
+            ln();
+            popNest();
+            ind().put("</boneRotEyxz>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * モーフデータを出力する。
+     * @param vmdMotion モーションデータ
+     * @throws IOException 出力エラー
+     */
+    private void putMorphSequence(VmdMotion vmdMotion)
+            throws IOException{
+        ind().put("<morphSequence>").ln();
+
+        pushNest();
+        NamedListMap<MorphMotion> listmap = vmdMotion.getMorphPartMap();
+        if( ! listmap.isEmpty() ) ln();
+        putMorphPartList(listmap);
+        popNest();
+
+        ind().put("</morphSequence>").ln(2);
+
+        return;
+    }
+
+    /**
+     * 箇所別モーフデータを出力する。
+     * @param listmap モーフデータの名前付きリストマップ
+     * @throws IOException 出力エラー
+     */
+    private void putMorphPartList(NamedListMap<MorphMotion> listmap)
+            throws IOException{
+        for(String morphName : listmap.getNames()){
+            if(VmdConst.isBaseMorphName(morphName)) continue;
+
+            ind().putLineComment(morphName);
+            String globalName = TypicalMorph.primary2global(morphName);
+            if(globalName != null){
+                sp(2).putLineComment("Perhaps : [" + globalName + "]");
+            }
+            ln();
+
+            ind().put("<morphPart ");
+            putAttr("name", morphName).sp();
+            put(">").ln();
+
+            pushNest();
+            List<MorphMotion> list = listmap.getNamedList(morphName);
+            for(MorphMotion morph : list){
+                putMorphMotion(morph);
+            }
+            popNest();
+
+            ind().put("</morphPart>").ln(2);
+        }
+
+        return;
+    }
+
+    /**
+     * 個別のモーフモーションを出力する。
+     * @param morphMotion モーフモーション
+     * @throws IOException 出力エラー
+     */
+    private void putMorphMotion(MorphMotion morphMotion)
+            throws IOException{
+        ind().put("<morphMotion ");
+
+        int frameNo = morphMotion.getFrameNumber();
+        float flex = morphMotion.getFlex();
+
+        putIntAttr("frame", frameNo).sp();
+        putFloatAttr("flex", flex).sp();
+
+        put("/>").ln();
+
+        return;
+    }
+
+    /**
+     * カメラ演出データを出力する。
+     * @param vmdMotion 演出データ
+     * @throws IOException 出力エラー
+     */
+    private void putCameraSequence(VmdMotion vmdMotion)
+            throws IOException{
+        ind().putBlockComment(BEZIER_COMMENT);
+        ind().putBlockComment(CAMERA_COMMENT);
+
+        ind().put("<cameraSequence>").ln();
+
+        pushNest();
+        List<CameraMotion> list = vmdMotion.getCameraMotionList();
+        if( ! list.isEmpty() ) ln();
+        for(CameraMotion camera : list){
+            putCameraMotion(camera);
+        }
+        popNest();
+
+        ind().put("</cameraSequence>").ln(2);
+
+        return;
+    }
+
+    /**
+     * カメラモーションを出力する。
+     * @param cameraMotion カメラモーション
+     * @throws IOException 出力エラー
+     */
+    private void putCameraMotion(CameraMotion cameraMotion)
+            throws IOException{
+        ind().put("<cameraMotion ");
+        int frameNo = cameraMotion.getFrameNumber();
+        putIntAttr("frame", frameNo).sp();
+        if( ! cameraMotion.hasPerspective() ){
+            putAttr("hasPerspective", "false").sp();
+        }
+        put(">").ln();
+
+        pushNest();
+        putCameraTarget(cameraMotion);
+        putCameraRotation(cameraMotion);
+        putCameraRange(cameraMotion);
+        putProjection(cameraMotion);
+        popNest();
+
+        ind().put("</cameraMotion>").ln(2);
+
+        return;
+    }
+
+    /**
+     * カメラターゲット情報を出力する。
+     * @param cameraMotion カメラモーション
+     * @throws IOException 出力エラー
+     */
+    private void putCameraTarget(CameraMotion cameraMotion)
+            throws IOException{
+        ind().put("<cameraTarget ");
+        MkPos3D position = cameraMotion.getCameraTarget();
+        putFloatAttr("xPos", (float) position.getXpos()).sp();
+        putFloatAttr("yPos", (float) position.getYpos()).sp();
+        putFloatAttr("zPos", (float) position.getZpos()).sp();
+
+        PosCurve posCurve = cameraMotion.getTargetPosCurve();
+        if(posCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+
+            pushNest();
+            putPositionCurve(posCurve);
+            popNest();
+
+            ind().put("</cameraTarget>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * カメラ回転情報を出力する。
+     * @param cameraMotion カメラモーション
+     * @throws IOException 出力エラー
+     */
+    private void putCameraRotation(CameraMotion cameraMotion)
+            throws IOException{
+        ind().put("<cameraRotation ");
+        CameraRotation rotation = cameraMotion.getCameraRotation();
+        putFloatAttr("xRad", rotation.getLatitude()).sp();
+        putFloatAttr("yRad", rotation.getLongitude()).sp();
+        putFloatAttr("zRad", rotation.getRoll()).sp();
+
+        BezierParam rotCurve = cameraMotion.getIntpltRotation();
+        if(rotCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+            pushNest();
+            ind();
+            putBezierCurve(rotCurve);
+            ln();
+            popNest();
+            ind().put("</cameraRotation>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * カメラ距離情報を出力する。
+     * @param cameraMotion カメラモーション
+     * @throws IOException 出力エラー
+     */
+    private void putCameraRange(CameraMotion cameraMotion)
+            throws IOException{
+        ind().put("<cameraRange ");
+        float range = cameraMotion.getRange();
+        putFloatAttr("range", range).sp();
+
+        BezierParam rangeCurve = cameraMotion.getIntpltRange();
+        if(rangeCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+            pushNest();
+            ind();
+            putBezierCurve(rangeCurve);
+            ln();
+            popNest();
+            ind().put("</cameraRange>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * スクリーン投影情報を出力する。
+     * @param cameraMotion カメラモーション
+     * @throws IOException 出力エラー
+     */
+    private void putProjection(CameraMotion cameraMotion)
+            throws IOException{
+        ind().put("<projection ");
+        putIntAttr("vertDeg", cameraMotion.getProjectionAngle()).sp();
+
+        BezierParam projCurve = cameraMotion.getIntpltProjection();
+        if(projCurve.isDefaultLinear()){
+            put("/>").ln();
+        }else{
+            put(">").ln();
+            pushNest();
+            ind();
+            putBezierCurve(projCurve);
+            ln();
+            popNest();
+            ind().put("</projection>").ln();
+        }
+
+        return;
+    }
+
+    /**
+     * 照明演出データを出力する。
+     * @param vmdMotion 演出データ
+     * @throws IOException 出力エラー
+     */
+    private void putLuminousSequence(VmdMotion vmdMotion)
+            throws IOException{
+        ind().put("<luminousSequence>").ln();
+
+        pushNest();
+        List<LuminousMotion> list = vmdMotion.getLuminousMotionList();
+        if( ! list.isEmpty() ) ln();
+        for(LuminousMotion luminous : list){
+            putLuminousMotion(luminous);
+        }
+        popNest();
+
+        ind().put("</luminousSequence>").ln(2);
+
+        return;
+    }
+
+    /**
+     * 照明モーションを出力する。
+     * @param luminousMotion 照明モーション
+     * @throws IOException 出力エラー
+     */
+    private void putLuminousMotion(LuminousMotion luminousMotion)
+            throws IOException{
+        ind().put("<luminousAct ");
+        int frameNo = luminousMotion.getFrameNumber();
+        putIntAttr("frame", frameNo);
+        put(" >").ln();
+
+        LuminousColor color = luminousMotion.getColor();
+        LuminousVector vector = luminousMotion.getDirection();
+
+        pushNest();
+        putLuminousColor(color);
+        putLuminousDirection(vector);
+        popNest();
+
+        ind().put("</luminousAct>").ln(2);
+
+        return;
+    }
+
+    /**
+     * 光源色情報を出力する。
+     * @param color 光源色
+     * @throws IOException 出力エラー
+     */
+    private void putLuminousColor(LuminousColor color)
+            throws IOException{
+        ind().put("<lumiColor ");
+        putFloatAttr("rCol", color.getColR()).sp();
+        putFloatAttr("gCol", color.getColG()).sp();
+        putFloatAttr("bCol", color.getColB()).sp();
+        put("/>").ln();
+
+        return;
+    }
+
+    /**
+     * 照明方向情報を出力する。
+     * @param vector 照明方向
+     * @throws IOException 出力エラー
+     */
+    private void putLuminousDirection(LuminousVector vector)
+            throws IOException{
+        ind().put("<lumiDirection ");
+        putFloatAttr("xVec", vector.getVecX()).sp();
+        putFloatAttr("yVec", vector.getVecY()).sp();
+        putFloatAttr("zVec", vector.getVecZ()).sp();
+        put("/>").ln();
+
+        return;
+    }
+
+    /**
+     * シャドウ演出データを出力する。
+     * @param vmdMotion 演出データ
+     * @throws IOException 出力エラー
+     */
+    private void putShadowSequence(VmdMotion vmdMotion)
+            throws IOException{
+        ind().putBlockComment(SHADOW_COMMENT);
+
+        ind().put("<shadowSequence>").ln();
+
+        pushNest();
+        List<ShadowMotion> list = vmdMotion.getShadowMotionList();
+        for(ShadowMotion shadow : list){
+            putShadowMotion(shadow);
+        }
+        popNest();
+
+        ind().put("</shadowSequence>").ln(2);
+
+        return;
+    }
+
+    /**
+     * シャドウモーションを出力する。
+     * @param shadowMotion シャドウモーション
+     * @throws IOException 出力エラー
+     */
+    private void putShadowMotion(ShadowMotion shadowMotion)
+            throws IOException{
+        ind().put("<shadowAct ");
+
+        int frameNo = shadowMotion.getFrameNumber();
+        ShadowMode mode = shadowMotion.getShadowMode();
+        float rawParam = shadowMotion.getRawScopeParam();
+
+        putIntAttr("frame", frameNo).sp();
+        putAttr("mode", mode.name()).sp();
+        putFloatAttr("rawParam", rawParam).sp();
+
+        put("/>");
+
+        double uiVal = ShadowMotion.rawParamToScope(rawParam);
+        long lVal = Math.round(uiVal);
+        sp().putLineComment("UI:" + lVal).ln();
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/VmdXmlResources.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/VmdXmlResources.java
new file mode 100644 (file)
index 0000000..b602ade
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * xml resources for VMD-XML
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+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.w3c.dom.ls.LSResourceResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * モーションデータ用各種XMLリソースの定義。
+ */
+public final class VmdXmlResources {
+
+    /** 定義の版数。 */
+    public static final String VER_VMDXML =
+            "110820";
+    /** XML名前空間識別子。 */
+    public static final String NS_VMDXML =
+            "http://mikutoga.sourceforge.jp/xml/ns/vmdxml/110820";
+    /** XMLスキーマURI名。 */
+    public static final String SCHEMAURI_VMDXML =
+            "http://mikutoga.sourceforge.jp/xml/xsd/vmdxml-110820.xsd";
+    /** ローカルなスキーマファイル名。 */
+    public static final String LOCAL_SCHEMA_VMDXML =
+            "resources/vmdxml-110820.xsd";
+
+    /** XMLスキーマURI。 */
+    public static final URI URI_SCHEMA_VMDXML;
+    /** XMLスキーマのローカルリソース。 */
+    public static final URI RES_SCHEMA_VMDXML;
+
+
+    private static final Class<?> THISCLASS = VmdXmlResources.class;
+
+
+    static{
+        try{
+            URI_SCHEMA_VMDXML = URI.create(SCHEMAURI_VMDXML);
+            RES_SCHEMA_VMDXML =
+                    THISCLASS.getResource(LOCAL_SCHEMA_VMDXML).toURI();
+        }catch(URISyntaxException e){
+            throw new ExceptionInInitializerError(e);
+        }
+
+        new VmdXmlResources().hashCode();
+    }
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private VmdXmlResources(){
+        super();
+        assert this.getClass().equals(THISCLASS);
+        return;
+    }
+
+
+    /**
+     * ビルダの生成。
+     * @param handler エラーハンドラ
+     * @return ビルダ
+     */
+    public static DocumentBuilder newBuilder(ErrorHandler handler){
+        XmlResourceResolver resolver = createResolver();
+
+        Schema schema = createSchema(resolver);
+
+        DocumentBuilderFactory builderFactory = createBuilderFactory();
+        builderFactory.setSchema(schema);
+
+        DocumentBuilder builder;
+        try{
+            builder = builderFactory.newDocumentBuilder();
+        }catch(ParserConfigurationException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+        builder.setEntityResolver(resolver);
+        builder.setErrorHandler(handler);
+
+        return builder;
+    }
+
+    /**
+     * URI参照をローカルなリソースアクセスへとリダイレクトするリゾルバを生成する。
+     * @return リゾルバ
+     */
+    private static XmlResourceResolver createResolver(){
+        XmlResourceResolver resolver = new XmlResourceResolver();
+        resolver.putURIMap(URI_SCHEMA_VMDXML, RES_SCHEMA_VMDXML);
+//        resolver.putURIMap(URI_DTD_VMDXML, RES_DTD_VMDXML);
+
+        return resolver;
+    }
+
+    /**
+     * 検証用スキーマ(XML schema)を生成する。
+     * @param resolver リゾルバ
+     * @return スキーマ
+     */
+    private static Schema createSchema(LSResourceResolver resolver){
+        SchemaFactory schemaFactory =
+                SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        schemaFactory.setResourceResolver(resolver);
+
+        URL localXsd;
+        try{
+            localXsd = RES_SCHEMA_VMDXML.toURL();
+        }catch(MalformedURLException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        Schema schema;
+        try{
+            schema = schemaFactory.newSchema(localXsd);
+        }catch(SAXException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        return schema;
+    }
+
+    /**
+     * DocumentBuilderFavtoryを生成する。
+     * @return ファクトリ
+     */
+    private static DocumentBuilderFactory createBuilderFactory(){
+        DocumentBuilderFactory builderFactory =
+                DocumentBuilderFactory.newInstance();
+
+        builderFactory.setNamespaceAware(true);
+        builderFactory.setValidating(false);    // DTD validation off
+        builderFactory.setIgnoringComments(true);
+        try{
+            builderFactory.setXIncludeAware(true);
+        }catch(UnsupportedOperationException e){
+            // NOTHING
+            assert true;
+        }
+
+        return builderFactory;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/Xml.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/Xml.java
new file mode 100644 (file)
index 0000000..5bd1e72
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * xml common
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+import java.util.Iterator;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.xml.DomNsUtils;
+import jp.sourceforge.mikutoga.xml.SiblingElemIterator;
+import jp.sourceforge.mikutoga.xml.TogaXmlException;
+import org.w3c.dom.Element;
+
+/**
+ * XMLユーティリティ集。
+ * <p>VMDに特化したDomNsUtilsのWrapper群や共通要素の共通処理を含む。
+ * <p>要素に関する名前空間は{@link VmdXmlResources.NS_VMDXML}が暗黙で用いられる。
+ * <p>非グローバル属性に関する名前空間は{@NS_NULL}が暗黙で用いられる。
+ * @see jp.sourceforge.mikutoga.xml.DomNsUtils
+ */
+final class Xml {
+
+    /** 非グローバル属性用名前空間。 */
+    static final String NS_NULL = null;
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private Xml(){
+        assert false;
+        throw new AssertionError();
+    }
+
+
+    /**
+     * ローカル名が一致する要素か判定する。
+     * @param elem 要素
+     * @param localName ローカル名。
+     * @return ローカル名が一致する要素であればtrue
+     */
+    static boolean hasNsLocalNameElem(Element elem,
+                                      String localName ){
+        return DomNsUtils.hasNsLocalNameElem(elem,
+                                             VmdXmlResources.NS_VMDXML,
+                                             localName );
+    }
+
+    /**
+     * ローカル名に合致する最初の直下子要素を返す。
+     * @param parent 親要素
+     * @param localName 子要素名
+     * @return 最初の直下子要素。見つからなければnull。
+     */
+    static Element pickChild(Element parent, String localName){
+        return DomNsUtils.pickFirstChild(parent,
+                                         VmdXmlResources.NS_VMDXML,
+                                         localName );
+    }
+
+    /**
+     * ローカル名に合致する最初の直下子要素を返す。
+     * <p>見つからなければ例外を投げる。
+     * @param parent 親要素
+     * @param localName 子要素名
+     * @return 最初の直下子要素
+     * @throws TogaXmlException 1つも見つからなかった
+     */
+    static Element getChild(Element parent, String localName)
+            throws TogaXmlException{
+        return DomNsUtils.getFirstChild(parent,
+                                        VmdXmlResources.NS_VMDXML,
+                                        localName );
+    }
+
+    /**
+     * 指定された名前の子要素のforeachを返す。
+     * @param parent 親要素
+     * @param localName 子要素名
+     * @return 子要素のforeach
+     */
+    static Iterable<Element> eachChild(Element parent, String localName){
+        return DomNsUtils.getEachChild(parent,
+                                       VmdXmlResources.NS_VMDXML,
+                                       localName );
+    }
+
+    /**
+     * 要素からxsd:string型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return 文字列
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    static String getStringAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomNsUtils.getStringAttrNS(elem, NS_NULL, attrName);
+    }
+
+    /**
+     * 要素からxsd:boolean型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return 真ならtrue
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    static boolean getBooleanAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomNsUtils.getBooleanAttrNS(elem, NS_NULL, attrName);
+    }
+
+    /**
+     * 要素からxsd:integer型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return int値
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    static int getIntegerAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomNsUtils.getIntegerAttrNS(elem, NS_NULL, attrName);
+    }
+
+    /**
+     * 要素から符号付きbyte型整数属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return byte値
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    static byte getByteAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        int iVal = getIntegerAttr(elem, attrName);
+        byte result = (byte) iVal;
+        return result;
+    }
+
+    /**
+     * 要素からxsd:float型属性値を読み取る。
+     * @param elem 要素
+     * @param attrName 属性名
+     * @return float値
+     * @throws TogaXmlException 属性値が見つからなかった。
+     */
+    static float getFloatAttr(Element elem, String attrName)
+            throws TogaXmlException{
+        return DomNsUtils.getFloatAttrNS(elem, NS_NULL, attrName);
+    }
+
+    /**
+     * ベジェ曲線による補間カーブ記述を読み込む。
+     * @param elem defLinear,defEaseInOut,bezier要素のいずれか
+     * @param bezier ベジェ曲線
+     * @throws TogaXmlException 構文エラー
+     */
+    static void setBezier(Element elem, BezierParam bezier)
+            throws TogaXmlException{
+        byte p1x;
+        byte p1y;
+        byte p2x;
+        byte p2y;
+
+        if(hasNsLocalNameElem(elem, "defLinear")){
+            p1x = BezierParam.DEF_P1X;
+            p1y = BezierParam.DEF_P1Y;
+            p2x = BezierParam.DEF_P2X;
+            p2y = BezierParam.DEF_P2Y;
+        }else if(hasNsLocalNameElem(elem, "defEaseInOut")){
+            p1x = BezierParam.EIO_P1X;
+            p1y = BezierParam.EIO_P1Y;
+            p2x = BezierParam.EIO_P2X;
+            p2y = BezierParam.EIO_P2Y;
+        }else if(hasNsLocalNameElem(elem, "bezier")){
+            p1x = getByteAttr(elem, "p1x");
+            p1y = getByteAttr(elem, "p1y");
+            p2x = getByteAttr(elem, "p2x");
+            p2y = getByteAttr(elem, "p2y");
+        }else{
+            assert false;
+            throw new AssertionError();
+        }
+
+        bezier.setP1(p1x, p1y);
+        bezier.setP2(p2x, p2y);
+
+        return;
+    }
+
+    /**
+     * 補間カーブ情報を読み込む。
+     * @param parentElem 親要素
+     * @param bezier 補間カーブ
+     * @throws TogaXmlException 構文エラー
+     */
+    static void buildCurve(Element parentElem, BezierParam bezier)
+            throws TogaXmlException{
+        Element bezierElem = pickChild(parentElem, null);
+        if(bezierElem == null) return;
+
+        setBezier(bezierElem, bezier);
+
+        return;
+    }
+
+    /**
+     * 位置補間情報を読み込む。
+     * @param positionElem 親要素
+     * @param curve 位置補間カーブ
+     * @throws TogaXmlException 構文エラー
+     */
+    static void buildPosCurve(Element positionElem, PosCurve curve)
+            throws TogaXmlException{
+        Iterator<Element> it =
+                new SiblingElemIterator(positionElem,
+                                        VmdXmlResources.NS_VMDXML, null);
+
+        int ct = 0;
+        while(it.hasNext()){
+            Element curveElem = it.next();
+            BezierParam bz = curve.item(ct);
+            setBezier(curveElem, bz);
+            ct++;
+        }
+
+        assert ct == 0 || ct == 3;
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/Xml2VmdLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/Xml2VmdLoader.java
new file mode 100644 (file)
index 0000000..287ad25
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * xml loader
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import javax.xml.parsers.DocumentBuilder;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.math.MkQuat;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.BoneMotion;
+import jp.sourceforge.mikutoga.vmd.model.MorphMotion;
+import jp.sourceforge.mikutoga.vmd.model.NamedListMap;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.xml.TogaXmlException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * XML形式でのモーションファイルを読み込む。
+ */
+public class Xml2VmdLoader {
+
+    private static final String ERRMSG_INVROOT =
+            "RootElem:[{0}] must be [vmdMotion]";
+
+
+    private final DocumentBuilder builder;
+
+
+    /**
+     * コンストラクタ。
+     * @param builder ビルダ
+     */
+    public Xml2VmdLoader(DocumentBuilder builder){
+        super();
+        assert builder.isNamespaceAware();
+        this.builder = builder;
+        return;
+    }
+
+
+    /**
+     * ルート要素の取得とチェックを行う。
+     * @param document XMLドキュメント
+     * @return ルート要素
+     * @throws TogaXmlException 不正なルート要素の検出
+     */
+    private static Element getRootElem(Document document)
+            throws TogaXmlException {
+        Element vmdMotionElem = document.getDocumentElement();
+        if( ! Xml.hasNsLocalNameElem(vmdMotionElem, "vmdMotion") ){
+            String message =
+                    MessageFormat.format(ERRMSG_INVROOT,
+                                         vmdMotionElem.getLocalName() );
+            throw new TogaXmlException(message);
+        }
+        return vmdMotionElem;
+    }
+
+    /**
+     * モーションのモデル名を読み込む。
+     * @param vmdMotionElem vmdMotion要素
+     * @param vmdMotion モーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildModelName(Element vmdMotionElem,
+                                       VmdMotion vmdMotion)
+            throws TogaXmlException{
+        Element modelNameElem = Xml.getChild(vmdMotionElem, "modelName");
+        String modelName = Xml.getStringAttr(modelNameElem, "name");
+        vmdMotion.setModelName(modelName);
+        return;
+    }
+
+    /**
+     * ボーンシーケンスを読み込む。
+     * @param vmdMotionElem vmdMotion要素
+     * @param vmdMotion モーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildBoneSeq(Element vmdMotionElem,
+                                     VmdMotion vmdMotion )
+            throws TogaXmlException{
+        NamedListMap<BoneMotion> boneMap = vmdMotion.getBonePartMap();
+
+        Element boneSeqElem =
+                Xml.getChild(vmdMotionElem, "boneMotionSequence");
+
+        for(Element bonePartElem : Xml.eachChild(boneSeqElem, "bonePart")){
+            buildBonePart(bonePartElem, boneMap);
+        }
+
+        return;
+    }
+
+    /**
+     * ボーンパートを読み込む。
+     * @param bonePartElem bonePart要素
+     * @param boneMap 名前マップ
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildBonePart(Element bonePartElem,
+                                      NamedListMap<BoneMotion> boneMap )
+            throws TogaXmlException{
+        String boneName = Xml.getStringAttr(bonePartElem, "name");
+
+        for(Element boneMotionElem :
+                Xml.eachChild(bonePartElem, "boneMotion")){
+            BoneMotion boneMotion = buildBoneMotion(boneMotionElem);
+            boneMotion.setBoneName(boneName);
+            boneMap.addNamedElement(boneName, boneMotion);
+        }
+
+        return;
+    }
+
+    /**
+     * ボーンモーションを読み込む。
+     * @param boneMotionElem boneMotion要素
+     * @return ボーンモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static BoneMotion buildBoneMotion(Element boneMotionElem)
+            throws TogaXmlException {
+        BoneMotion boneMotion = new BoneMotion();
+        int frameNo = Xml.getIntegerAttr(boneMotionElem, "frame");
+        boneMotion.setFrameNumber(frameNo);
+
+        buildBonePosition(boneMotionElem, boneMotion);
+        if(Xml.pickChild(boneMotionElem, "boneRotQuat") != null){
+            buildBoneRotQuat(boneMotionElem, boneMotion);
+        }else{
+            buildBoneRotEyxz(boneMotionElem, boneMotion);
+        }
+
+        return boneMotion;
+    }
+
+    /**
+     * ボーン位置を読み込む。
+     * @param boneMotionElem boneMotion要素
+     * @param boneMotion ボーンモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildBonePosition(Element boneMotionElem,
+                                          BoneMotion boneMotion )
+            throws TogaXmlException {
+        Element bonePositionElem =
+                Xml.pickChild(boneMotionElem, "bonePosition");
+        if(bonePositionElem == null) return;
+
+        MkPos3D position = boneMotion.getPosition();
+        float xPos = Xml.getFloatAttr(bonePositionElem, "xPos");
+        float yPos = Xml.getFloatAttr(bonePositionElem, "yPos");
+        float zPos = Xml.getFloatAttr(bonePositionElem, "zPos");
+        position.setXpos(xPos);
+        position.setYpos(yPos);
+        position.setZpos(zPos);
+
+        PosCurve curve = boneMotion.getPosCurve();
+        Xml.buildPosCurve(bonePositionElem, curve);
+
+        return;
+    }
+
+    /**
+     * ボーン回転をクォータニオン形式で読み込む。
+     * @param boneMotionElem boneMotion要素
+     * @param boneMotion ボーンモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildBoneRotQuat(Element boneMotionElem,
+                                          BoneMotion boneMotion )
+            throws TogaXmlException{
+        Element boneRotationElem =
+                Xml.getChild(boneMotionElem, "boneRotQuat");
+
+        MkQuat rotation = boneMotion.getRotation();
+        float qx = Xml.getFloatAttr(boneRotationElem, "qx");
+        float qy = Xml.getFloatAttr(boneRotationElem, "qy");
+        float qz = Xml.getFloatAttr(boneRotationElem, "qz");
+        float qw = Xml.getFloatAttr(boneRotationElem, "qw");
+
+        rotation.setQ1(qx);
+        rotation.setQ2(qy);
+        rotation.setQ3(qz);
+        rotation.setQW(qw);
+
+        BezierParam rotationCurve = boneMotion.getIntpltRotation();
+        Xml.buildCurve(boneRotationElem, rotationCurve);
+
+        return;
+    }
+
+    /**
+     * ボーン回転をオイラー角で読み込む。
+     * @param boneMotionElem boneMotion要素
+     * @param boneMotion ボーンモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildBoneRotEyxz(Element boneMotionElem,
+                                         BoneMotion boneMotion )
+            throws TogaXmlException{
+        Element boneRotationElem =
+                Xml.getChild(boneMotionElem, "boneRotEyxz");
+
+        MkQuat rotation = boneMotion.getRotation();
+
+        float xDeg = Xml.getFloatAttr(boneRotationElem, "xDeg");
+        float yDeg = Xml.getFloatAttr(boneRotationElem, "yDeg");
+        float zDeg = Xml.getFloatAttr(boneRotationElem, "zDeg");
+        float xRad = (float)StrictMath.toRadians(xDeg);
+        float yRad = (float)StrictMath.toRadians(yDeg);
+        float zRad = (float)StrictMath.toRadians(zDeg);
+        rotation.readEulerYXZ(xRad, yRad, zRad);
+
+        BezierParam rotationCurve = boneMotion.getIntpltRotation();
+        Xml.buildCurve(boneRotationElem, rotationCurve);
+
+        return;
+    }
+
+    /**
+     * モーフシーケンスを読み込む。
+     * @param vmdMotionElem vmdMotion要素
+     * @param vmdMotion モーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildMorphSeq(Element vmdMotionElem,
+                                      VmdMotion vmdMotion )
+            throws TogaXmlException{
+        NamedListMap<MorphMotion> morphMap = vmdMotion.getMorphPartMap();
+
+        Element morphSeqElem = Xml.getChild(vmdMotionElem, "morphSequence");
+
+        for(Element morphPartElem :
+                Xml.eachChild(morphSeqElem, "morphPart")){
+            buildMorphPart(morphPartElem, morphMap);
+        }
+
+        return;
+    }
+
+    /**
+     * モーフパートを読み込む。
+     * @param morphPartElem morphPart要素
+     * @param morphMap 名前マップ
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildMorphPart(Element morphPartElem,
+                                       NamedListMap<MorphMotion> morphMap )
+            throws TogaXmlException{
+        String morphName = Xml.getStringAttr(morphPartElem, "name");
+
+        Iterable<Element> childs =
+                Xml.eachChild(morphPartElem, "morphMotion");
+        for(Element morphMotionElem : childs){
+            MorphMotion morphMotion = buildMorphMotion(morphMotionElem);
+            morphMotion.setMorphName(morphName);
+            morphMap.addNamedElement(morphName, morphMotion);
+        }
+
+        return;
+    }
+
+    /**
+     * モーフモーションを読み込む。
+     * @param morphMotionElem morphMotion要素
+     * @return モーフモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static MorphMotion buildMorphMotion(Element morphMotionElem)
+            throws TogaXmlException {
+        MorphMotion morphMotion = new MorphMotion();
+
+        int frameNo = Xml.getIntegerAttr(morphMotionElem, "frame");
+        float flex = Xml.getFloatAttr(morphMotionElem, "flex");
+
+        morphMotion.setFrameNumber(frameNo);
+        morphMotion.setFlex(flex);
+
+        return morphMotion;
+    }
+
+    /**
+     * XMLのパースを開始する。
+     * @param source XML入力
+     * @return モーションデータ
+     * @throws SAXException 構文エラー
+     * @throws IOException 入力エラー
+     * @throws TogaXmlException 構文エラー
+     */
+    public VmdMotion parse(InputSource source)
+            throws SAXException, IOException, TogaXmlException{
+        Document document = this.builder.parse(source);
+        Element vmdMotionElem = getRootElem(document);
+        VmdMotion vmdMotion = new VmdMotion();
+
+        // ignore <meta>
+
+        if(Xml.pickChild(vmdMotionElem, "modelName") != null){
+            buildModelName(vmdMotionElem, vmdMotion);
+            buildBoneSeq(vmdMotionElem, vmdMotion);
+            buildMorphSeq(vmdMotionElem, vmdMotion);
+        }else{
+            XmlCameraLoader.buildCameraSeq(vmdMotionElem, vmdMotion);
+            XmlLightingLoader.buildLuminousSeq(vmdMotionElem, vmdMotion);
+            XmlLightingLoader.buildShadowSeq(vmdMotionElem, vmdMotion);
+        }
+
+        return vmdMotion;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/XmlCameraLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/XmlCameraLoader.java
new file mode 100644 (file)
index 0000000..78a87f4
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * xml camera loader
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+import java.util.List;
+import jp.sourceforge.mikutoga.math.MkPos3D;
+import jp.sourceforge.mikutoga.vmd.model.BezierParam;
+import jp.sourceforge.mikutoga.vmd.model.CameraMotion;
+import jp.sourceforge.mikutoga.vmd.model.CameraRotation;
+import jp.sourceforge.mikutoga.vmd.model.PosCurve;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.xml.TogaXmlException;
+import org.w3c.dom.Element;
+
+/**
+ * XMLによるカメラ制御データを読み取る。
+ */
+final class XmlCameraLoader {
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private XmlCameraLoader(){
+        assert false;
+        throw new AssertionError();
+    }
+
+
+    /**
+     * カメラシーケンスを読み込む。
+     * @param vmdMotionElem vmdMotion要素
+     * @param vmdMotion モーション
+     * @throws TogaXmlException 構文エラー
+     */
+    static void buildCameraSeq(Element vmdMotionElem, VmdMotion vmdMotion)
+            throws TogaXmlException{
+        List<CameraMotion> cameraList = vmdMotion.getCameraMotionList();
+
+        Element cameraSeqElem =
+                Xml.getChild(vmdMotionElem, "cameraSequence");
+
+        Iterable<Element> childs =
+                Xml.eachChild(cameraSeqElem, "cameraMotion");
+        for(Element cameraMotionElem : childs){
+            buildCameraMotion(cameraMotionElem, cameraList);
+        }
+
+        return;
+    }
+
+    /**
+     * カメラモーションを読み込む。
+     * @param cameraMotionElem cameraMotion要素
+     * @param cameraList カメラモーションリスト
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildCameraMotion(Element cameraMotionElem,
+                                          List<CameraMotion> cameraList)
+            throws TogaXmlException{
+        CameraMotion cameraMotion = new CameraMotion();
+
+        int frameNo = Xml.getIntegerAttr(cameraMotionElem, "frame");
+        cameraMotion.setFrameNumber(frameNo);
+
+        if(cameraMotionElem.hasAttributeNS(Xml.NS_NULL, "hasPerspective")){
+            boolean hasPerspective =
+                    Xml.getBooleanAttr(cameraMotionElem, "hasPerspective");
+            cameraMotion.setPerspectiveMode(hasPerspective);
+        }
+
+        buildCameraTarget(cameraMotionElem, cameraMotion);
+        buildCameraRotation(cameraMotionElem, cameraMotion);
+        buildCameraRange(cameraMotionElem, cameraMotion);
+        buildProjection(cameraMotionElem, cameraMotion);
+
+        cameraList.add(cameraMotion);
+
+        return;
+    }
+
+    /**
+     * カメラターゲット情報を読み込む。
+     * @param cameraMotionElem cameraMotion要素
+     * @param cameraMotion カメラモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildCameraTarget(Element cameraMotionElem,
+                                          CameraMotion cameraMotion)
+            throws TogaXmlException{
+        Element cameraTargetElem =
+                Xml.getChild(cameraMotionElem, "cameraTarget");
+        MkPos3D targetPos = cameraMotion.getCameraTarget();
+
+        float xPos = Xml.getFloatAttr(cameraTargetElem, "xPos");
+        float yPos = Xml.getFloatAttr(cameraTargetElem, "yPos");
+        float zPos = Xml.getFloatAttr(cameraTargetElem, "zPos");
+        targetPos.setPosition(xPos, yPos, zPos);
+
+        PosCurve curve = cameraMotion.getTargetPosCurve();
+        Xml.buildPosCurve(cameraTargetElem, curve);
+
+        return;
+    }
+
+    /**
+     * カメラ回転情報を読み込む。
+     * @param cameraMotionElem cameraMotion要素
+     * @param cameraMotion カメラモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildCameraRotation(Element cameraMotionElem,
+                                            CameraMotion cameraMotion)
+            throws TogaXmlException{
+        Element cameraRotationElem =
+                Xml.getChild(cameraMotionElem, "cameraRotation");
+        CameraRotation cameraRotation = cameraMotion.getCameraRotation();
+
+        float latitude  = Xml.getFloatAttr(cameraRotationElem, "xRad");
+        float longitude = Xml.getFloatAttr(cameraRotationElem, "yRad");
+        float roll      = Xml.getFloatAttr(cameraRotationElem, "zRad");
+        cameraRotation.setLatitude(latitude);
+        cameraRotation.setLongitude(longitude);
+        cameraRotation.setRoll(roll);
+
+        BezierParam rotationCurve = cameraMotion.getIntpltRotation();
+        Xml.buildCurve(cameraRotationElem, rotationCurve);
+
+        return;
+    }
+
+    /**
+     * カメラ距離情報を読み込む。
+     * @param cameraMotionElem cameraMotion要素
+     * @param cameraMotion カメラモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildCameraRange(Element cameraMotionElem,
+                                         CameraMotion cameraMotion)
+            throws TogaXmlException{
+        Element cameraRangeElem =
+                Xml.getChild(cameraMotionElem, "cameraRange");
+
+        float range = Xml.getFloatAttr(cameraRangeElem, "range");
+        cameraMotion.setRange(range);
+
+        BezierParam rangeCurve = cameraMotion.getIntpltRange();
+        Xml.buildCurve(cameraRangeElem, rangeCurve);
+
+        return;
+    }
+
+    /**
+     * カメラ投影情報を読み込む。
+     * @param cameraMotionElem cameraMotion要素
+     * @param cameraMotion カメラモーション
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildProjection(Element cameraMotionElem,
+                                        CameraMotion cameraMotion)
+            throws TogaXmlException{
+        Element projectionElem =
+                Xml.getChild(cameraMotionElem, "projection");
+
+        int vertDeg = Xml.getIntegerAttr(projectionElem, "vertDeg");
+        cameraMotion.setProjectionAngle(vertDeg);
+
+        BezierParam projCurve = cameraMotion.getIntpltProjection();
+        Xml.buildCurve(projectionElem, projCurve);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/XmlLightingLoader.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/XmlLightingLoader.java
new file mode 100644 (file)
index 0000000..49cb91b
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * xml lighting loader
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+import java.util.List;
+import jp.sourceforge.mikutoga.vmd.model.LuminousColor;
+import jp.sourceforge.mikutoga.vmd.model.LuminousMotion;
+import jp.sourceforge.mikutoga.vmd.model.LuminousVector;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMode;
+import jp.sourceforge.mikutoga.vmd.model.ShadowMotion;
+import jp.sourceforge.mikutoga.vmd.model.VmdMotion;
+import jp.sourceforge.mikutoga.xml.TogaXmlException;
+import org.w3c.dom.Element;
+
+/**
+ * XMLによるカメラ制御データを読み取る。
+ */
+final class XmlLightingLoader {
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private XmlLightingLoader(){
+        assert false;
+        throw new AssertionError();
+    }
+
+
+    /**
+     * 照明シーケンスを読み込む。
+     * @param vmdMotionElem vmdMotion要素
+     * @param vmdMotion モーション
+     * @throws TogaXmlException 構文エラー
+     */
+    static void buildLuminousSeq(Element vmdMotionElem, VmdMotion vmdMotion)
+            throws TogaXmlException{
+        List<LuminousMotion> luminousList =
+                vmdMotion.getLuminousMotionList();
+
+        Element luminousSeqElem =
+                Xml.getChild(vmdMotionElem, "luminousSequence");
+
+        Iterable<Element> childs =
+                Xml.eachChild(luminousSeqElem, "luminousAct");
+        for(Element luminousActElem : childs){
+            buildLuminousAct(luminousActElem, luminousList);
+        }
+
+        return;
+    }
+
+    /**
+     * 照明モーションを読み込む。
+     * @param luminousActElem luminousAct要素
+     * @param luminousList 照明モーションリスト
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildLuminousAct(Element luminousActElem,
+                                         List<LuminousMotion> luminousList)
+            throws TogaXmlException{
+        LuminousMotion luminousMotion = new LuminousMotion();
+
+        int frameNo = Xml.getIntegerAttr(luminousActElem, "frame");
+        luminousMotion.setFrameNumber(frameNo);
+
+        Element lumiColorElem = Xml.getChild(luminousActElem, "lumiColor");
+        LuminousColor color = luminousMotion.getColor();
+        float rCol = Xml.getFloatAttr(lumiColorElem, "rCol");
+        float gCol = Xml.getFloatAttr(lumiColorElem, "gCol");
+        float bCol = Xml.getFloatAttr(lumiColorElem, "bCol");
+        color.setColR(rCol);
+        color.setColG(gCol);
+        color.setColB(bCol);
+
+        Element lumiDirectionElem =
+                Xml.getChild(luminousActElem, "lumiDirection");
+        LuminousVector vec = luminousMotion.getDirection();
+        float xVec = Xml.getFloatAttr(lumiDirectionElem, "xVec");
+        float yVec = Xml.getFloatAttr(lumiDirectionElem, "yVec");
+        float zVec = Xml.getFloatAttr(lumiDirectionElem, "zVec");
+        vec.setVecX(xVec);
+        vec.setVecY(yVec);
+        vec.setVecZ(zVec);
+
+        luminousList.add(luminousMotion);
+
+        return;
+    }
+
+    /**
+     * シャドウシーケンスを読み込む。
+     * @param vmdMotionElem vmdMotion要素
+     * @param vmdMotion モーション
+     * @throws TogaXmlException 構文エラー
+     */
+    static void buildShadowSeq(Element vmdMotionElem,
+                               VmdMotion vmdMotion)
+            throws TogaXmlException{
+        List<ShadowMotion> shadowMotionList =
+                vmdMotion.getShadowMotionList();
+
+        Element shadowSeqElem =
+                Xml.getChild(vmdMotionElem, "shadowSequence");
+
+        for(Element shadowActElem :
+                Xml.eachChild(shadowSeqElem, "shadowAct")){
+            buildShadowAct(shadowActElem, shadowMotionList);
+        }
+
+        return;
+    }
+
+    /**
+     * シャドウモーションを読み込む。
+     * @param shadowActElem shadowAct要素
+     * @param shadowMotionList シャドウモーションリスト
+     * @throws TogaXmlException 構文エラー
+     */
+    private static void buildShadowAct(Element shadowActElem,
+                                       List<ShadowMotion> shadowMotionList)
+            throws TogaXmlException{
+        ShadowMotion shadowMotion = new ShadowMotion();
+
+        int frameNo = Xml.getIntegerAttr(shadowActElem, "frame");
+        shadowMotion.setFrameNumber(frameNo);
+
+        float rawParam = Xml.getFloatAttr(shadowActElem, "rawParam");
+        shadowMotion.setRawScopeParam(rawParam);
+
+        String modeAttr = Xml.getStringAttr(shadowActElem, "mode");
+        ShadowMode mode = ShadowMode.valueOf(modeAttr);
+        shadowMotion.setShadowMode(mode);
+
+        shadowMotionList.add(shadowMotion);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/package-info.java b/src/main/java/jp/sourceforge/mikutoga/vmd/model/xml/package-info.java
new file mode 100644 (file)
index 0000000..fba3b9c
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+/**
+ * VMDモーション独自データモデルとXML間でデータ交換を行うためのライブラリ。
+ */
+
+package jp.sourceforge.mikutoga.vmd.model.xml;
+
+/* EOF */
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/package-info.java b/src/main/java/jp/sourceforge/mikutoga/vmd/package-info.java
new file mode 100644 (file)
index 0000000..3aaa970
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+/**
+ * MikuMikuDanceのモーションデータ格納ファイル、
+ * VMDファイル(*.vmd)操作関連の共通資産。
+ */
+
+package jp.sourceforge.mikutoga.vmd;
+
+/* EOF */
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdBasicHandler.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdBasicHandler.java
new file mode 100644 (file)
index 0000000..c62d3c4
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * VMD basic handler
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import jp.sourceforge.mikutoga.parser.LoopHandler;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+
+/**
+ * VMDモーションファイルの基本情報(ボーンモーション/モーフモーション)
+ * の通知用ハンドラ。
+ * <p>フレーム番号は相対的なものとなる。
+ * 一番若いモーションのフレーム番号が0となる模様。
+ * <p>位置情報の座標基準は左手系ワールド座標で表される。
+ * <p>モーション補間情報は三次ベジェ曲線により記述される。
+ * 三次ベジェ曲線は4つの制御点P0,P1,P2,P3により定義される。
+ * P0は(0,0)、P3は(127,127)で固定。
+ * P1,P2はP0,P3を対角線とする正方形の内部になければならない。
+ * 直線補間の場合、P1には(20,20)、P2には(107,107)が使われることが多い。
+ * <p>補間情報は直後のボーンモーション情報との差分に関するもの。
+ */
+public interface VmdBasicHandler extends LoopHandler {
+
+    /** ボーンモーション抽出ループ識別子。 */
+    ParseStage BONEMOTION_LIST = new ParseStage();
+
+    /** モーフ抽出ループ識別子。 */
+    ParseStage MORPH_LIST = new ParseStage();
+
+
+    /**
+     * VMDファイルのパース処理開始の通知を受け取る。
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdParseStart()
+            throws MmdFormatException;
+
+    /**
+     * VMDファイルのパース処理終了の通知を受け取る。
+     * @param hasMoreData 入力ソースに
+     * まだ読み込まれていないデータがあればtrue
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdParseEnd(boolean hasMoreData)
+            throws MmdFormatException;
+
+    /**
+     * VMDファイルの固定長ヘッダを通知する。
+     * @param header ヘッダ情報
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdHeaderInfo(byte[] header)
+            throws MmdFormatException;
+
+
+    /**
+     * モーションの適用先モデル名に関する情報を通知する。
+     * <p>カメラやライティングなどの演出データには
+     * 特殊なモデル名が使われる。
+     * @param modelName モデル名
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdModelName(String modelName)
+            throws MmdFormatException;
+
+    /**
+     * ボーンモーションのボーン名及びキーフレーム番号に関する情報を通知する。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * @param boneName ボーン名
+     * @param keyFrameNo キーフレーム番号
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBoneMotion(String boneName, int keyFrameNo)
+            throws MmdFormatException;
+
+    /**
+     * ボーン移動モーション情報の通知を受け取る。
+     * <p>座標基準は親ボーンもしくはワールド座標。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * @param xPos X座標
+     * @param yPos Y座標
+     * @param zPos Z座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBonePosition(float xPos, float yPos, float zPos)
+            throws MmdFormatException;
+
+    /**
+     * ボーン回転モーション情報の通知を受け取る。
+     * <p>回転及び姿勢はクォータニオンによって記述される。
+     * MMDUI上のボーン数値入力YXZオイラー角と等価な回転。
+     * <p>座標基準は親ボーンもしくはワールド座標。
+     * <p>※ボーン種別によっては、無意味な情報。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * @param qx クォータニオン虚部 X
+     * @param qy クォータニオン虚部 Y
+     * @param qz クォータニオン虚部 Z
+     * @param qw クォータニオン実部 W
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBoneRotationQt(float qx, float qy, float qz, float qw)
+            throws MmdFormatException;
+
+    /**
+     * ボーンモーションX軸移動補間情報の通知を受け取る。
+     * <p>三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * @param xP1x P1点のX座標
+     * @param xP1y P1点のY座標
+     * @param xP2x P2点のX座標
+     * @param xP2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBoneIntpltXpos(byte xP1x, byte xP1y, byte xP2x, byte xP2y)
+            throws MmdFormatException;
+
+    /**
+     * ボーンモーションY軸移動補間情報の通知を受け取る。
+     * <p>三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * @param yP1x P1点のX座標
+     * @param yP1y P1点のY座標
+     * @param yP2x P2点のX座標
+     * @param yP2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBoneIntpltYpos(byte yP1x, byte yP1y, byte yP2x, byte yP2y)
+            throws MmdFormatException;
+
+    /**
+     * ボーンモーションZ軸移動補間情報の通知を受け取る。
+     * <p>三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * @param zP1x P1点のX座標
+     * @param zP1y P1点のY座標
+     * @param zP2x P2点のX座標
+     * @param zP2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBoneIntpltZpos(byte zP1x, byte zP1y, byte zP2x, byte zP2y)
+            throws MmdFormatException;
+
+    /**
+     * ボーンモーション回転量補間情報の通知を受け取る。
+     * <p>三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #BONEMOTION_LIST}ループの構成要素。
+     * <p>クォータニオン間のslerp処理に利用される。
+     * @param rP1x P1点のX座標
+     * @param rP1y P1点のY座標
+     * @param rP2x P2点のX座標
+     * @param rP2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdBoneIntpltRot(byte rP1x, byte rP1y, byte rP2x, byte rP2y)
+            throws MmdFormatException;
+
+
+    /**
+     * モーフモーション情報の通知を受け取る。
+     * <p>{@link #MORPH_LIST}ループの構成要素。
+     * @param morphName モーフ名。特殊モーフ名「base」は無視してもよい?
+     * @param keyFrameNo フレーム番号
+     * @param flex モーフ変量。通常は0.0以上1.0以下。
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdMorphMotion(String morphName, int keyFrameNo, float flex)
+            throws MmdFormatException;
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdBasicParser.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdBasicParser.java
new file mode 100644 (file)
index 0000000..4c15a5a
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * VMD basic parser
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import java.io.IOException;
+import jp.sourceforge.mikutoga.parser.CommonParser;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.MmdSource;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+
+/**
+ * VMDモーションファイルの基本部パーサ。
+ * <p>ボーンのモーション情報およびモーフモーション情報のパース処理を含む。
+ */
+class VmdBasicParser extends CommonParser{
+
+    private static final int BZ_SIZE = 4;           // 4byte Bezier parameter
+    private static final int BZXYZR_SIZE = BZ_SIZE * 4; // XYZR Bezier
+    private static final int BZ_REDUNDANT = 4;          // redundant spare
+    private static final int BZTOTAL_SIZE = BZXYZR_SIZE * BZ_REDUNDANT;
+
+    private static final String ERRMSG_INVINTPLT =
+            "there is potential inconsistency in motion interpolation data. "
+            +"(Strict-mode)";
+
+
+    private final byte[] motionIntplt = new byte[BZTOTAL_SIZE];
+
+    private VmdBasicHandler handler = null;
+
+    private boolean hasStageActName = false;
+    private boolean strictMode = true;
+
+
+    /**
+     * コンストラクタ。
+     * @param source 入力ソース
+     */
+    VmdBasicParser(MmdSource source){
+        super(source);
+        return;
+    }
+
+
+    /**
+     * パースしたモデル名がカメラ及びライティング用モデル名だったか判定する。
+     * @return カメラ及びライティング用モデル名だったらtrue
+     */
+    boolean hasStageActName(){
+        return this.hasStageActName;
+    }
+
+    /**
+     * 基本情報通知用ハンドラを登録する。
+     * @param basicHandler ハンドラ
+     */
+    void setBasicHandler(VmdBasicHandler basicHandler){
+        this.handler = basicHandler;
+        return;
+    }
+
+    /**
+     * 厳密なパース(Strict-mode)を行うか否か設定する。
+     * デフォルトではStrict-modeはオン。
+     * <p>Strict-mode下では、
+     * ボーンモーションの冗長な補間情報の一貫性チェックが行わる。
+     * @param mode Strict-modeに設定したければtrue
+     */
+    void setStrictMode(boolean mode){
+        this.strictMode = mode;
+        return;
+    }
+
+    /**
+     * VMDファイル基本部のパースを開始する。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    void parse() throws IOException, MmdFormatException {
+        this.hasStageActName = false;
+
+        parseVmdHeader();
+        parseVmdModelName();
+        parseVmdBoneMotion();
+        parseVmdMorph();
+
+        return;
+    }
+
+    /**
+     * VMDファイルヘッダ部のパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdHeader() throws IOException, MmdFormatException{
+        byte[] header = new byte[VmdConst.HEADER_LENGTH];
+        parseByteArray(header);
+
+        if( ! VmdConst.startsWithMagic(header) ){
+            throw new MmdFormatException("unknown VMD-header type");
+        }
+
+        if(this.handler != null){
+            this.handler.vmdHeaderInfo(header);
+        }
+
+        return;
+    }
+
+    /**
+     * モデル名のパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdModelName() throws IOException, MmdFormatException{
+        String modelName = parseZeroTermWin31J(VmdConst.MODELNAME_MAX);
+
+        if(VmdConst.isStageActName(modelName)){
+            this.hasStageActName = true;
+        }
+
+        if(this.handler != null){
+            this.handler.vmdModelName(modelName);
+        }
+
+        return;
+    }
+
+    /**
+     * ボーンモーションデータのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdBoneMotion()
+            throws IOException, MmdFormatException{
+        int boneMotionNo = parseInteger();
+
+        if(this.handler == null){
+            skip(VmdConst.BONEMOTION_DATA_SZ * boneMotionNo);
+            return;
+        }
+
+        this.handler.loopStart(
+                VmdBasicHandler.BONEMOTION_LIST, boneMotionNo);
+
+        for(int ct = 0; ct < boneMotionNo; ct++){
+            String boneName = parseZeroTermWin31J(VmdConst.BONENAME_MAX);
+            int keyFrameNo = parseInteger();
+            this.handler.vmdBoneMotion(boneName, keyFrameNo);
+
+            float xPos = parseFloat();
+            float yPos = parseFloat();
+            float zPos = parseFloat();
+            this.handler.vmdBonePosition(xPos, yPos, zPos);
+
+            float qx = parseFloat();
+            float qy = parseFloat();
+            float qz = parseFloat();
+            float qw = parseFloat();
+            this.handler.vmdBoneRotationQt(qx, qy, qz, qw);
+
+            parseVmdMotionInterpolation();
+
+            this.handler.loopNext(VmdBasicHandler.BONEMOTION_LIST);
+        }
+
+        this.handler.loopEnd(VmdBasicHandler.BONEMOTION_LIST);
+
+        return;
+    }
+
+    /**
+     * ボーンモーション補間データのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdMotionInterpolation()
+            throws IOException, MmdFormatException{
+        if(this.handler == null){
+            skip(this.motionIntplt.length);
+            return;
+        }
+
+        parseByteArray(this.motionIntplt);
+
+        if(this.strictMode){
+            checkIntpltStrict();
+        }
+
+        int idx = 0;
+
+        byte xP1x = this.motionIntplt[idx++];
+        byte yP1x = this.motionIntplt[idx++];
+        byte zP1x = this.motionIntplt[idx++];
+        byte rP1x = this.motionIntplt[idx++];
+
+        byte xP1y = this.motionIntplt[idx++];
+        byte yP1y = this.motionIntplt[idx++];
+        byte zP1y = this.motionIntplt[idx++];
+        byte rP1y = this.motionIntplt[idx++];
+
+        byte xP2x = this.motionIntplt[idx++];
+        byte yP2x = this.motionIntplt[idx++];
+        byte zP2x = this.motionIntplt[idx++];
+        byte rP2x = this.motionIntplt[idx++];
+
+        byte xP2y = this.motionIntplt[idx++];
+        byte yP2y = this.motionIntplt[idx++];
+        byte zP2y = this.motionIntplt[idx++];
+        byte rP2y = this.motionIntplt[idx++];
+
+        assert idx == BZXYZR_SIZE;
+
+        this.handler.vmdBoneIntpltXpos(xP1x, xP1y, xP2x, xP2y);
+        this.handler.vmdBoneIntpltYpos(yP1x, yP1y, yP2x, yP2y);
+        this.handler.vmdBoneIntpltZpos(zP1x, zP1y, zP2x, zP2y);
+        this.handler.vmdBoneIntpltRot (rP1x, rP1y, rP2x, rP2y);
+
+        return;
+    }
+
+    /**
+     * 補間情報の冗長箇所の整合性チェックを行う。
+     * @throws MmdFormatException 冗長箇所の不整合を検出した。
+     */
+    private void checkIntpltStrict() throws MmdFormatException{
+        int lack = 1;
+        for(int ct = 1; ct < BZ_REDUNDANT; ct++){
+            int sourceIdx = 0 + lack;
+            int targetIdx = BZXYZR_SIZE * ct;
+            int span = BZXYZR_SIZE - lack;
+
+            for(int idx = 0; idx < span; idx++){
+                byte sourceVal = this.motionIntplt[sourceIdx + idx];
+                byte targetVal = this.motionIntplt[targetIdx + idx];
+                if(sourceVal != targetVal){
+                    throw new MmdFormatException(ERRMSG_INVINTPLT,
+                                                 getPosition());
+                }
+            }
+
+            int onePos = targetIdx + span;
+            if(this.motionIntplt[onePos] != (byte) 0x01){
+                throw new MmdFormatException(ERRMSG_INVINTPLT, getPosition());
+            }
+
+            int zeroPosStart = onePos + 1;
+            int zeroPosEnd = targetIdx + BZXYZR_SIZE;
+            for(int idx = zeroPosStart; idx < zeroPosEnd; idx++){
+                if(this.motionIntplt[idx] != (byte) 0x00){
+                    throw new MmdFormatException(ERRMSG_INVINTPLT,
+                                                 getPosition());
+                }
+            }
+
+            lack++;
+        }
+
+        return;
+    }
+
+    /**
+     * モーフモーションデータのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdMorph() throws IOException, MmdFormatException{
+        int morphMotionNo = parseInteger();
+
+        if(this.handler == null){
+            skip(VmdConst.MORPH_DATA_SZ * morphMotionNo);
+            return;
+        }
+
+        this.handler.loopStart(
+                VmdBasicHandler.MORPH_LIST, morphMotionNo);
+
+        for(int ct = 0; ct < morphMotionNo; ct++){
+            String morphName = parseZeroTermWin31J(VmdConst.MORPHNAME_MAX);
+            int keyFrameNo = parseInteger();
+            float flex = parseFloat();
+            this.handler.vmdMorphMotion(morphName, keyFrameNo, flex);
+
+            this.handler.loopNext(VmdBasicHandler.MORPH_LIST);
+        }
+
+        this.handler.loopEnd(VmdBasicHandler.MORPH_LIST);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdCameraHandler.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdCameraHandler.java
new file mode 100644 (file)
index 0000000..7ac11a4
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * VMD camerawork handler
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import jp.sourceforge.mikutoga.parser.LoopHandler;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+
+/**
+ * VMDモーションファイルのカメラワーク情報の通知用ハンドラ。
+ * <p>フレーム番号は相対的なものとなる。
+ * カメラ・照明・シャドウのうち
+ * 一番若いモーションのフレーム番号が0となる模様。
+ * <p>ターゲット位置、カメラ回転量とも、座標基準はワールド座標。
+ * <p>カメラターゲットの座標は左手系で表される。
+ * <p>ターゲットに対するカメラ位置は極座標で表される。
+ * <p>補間情報は直前カメラ情報との差分に関するもの。
+ */
+public interface VmdCameraHandler extends LoopHandler {
+
+    /** カメラデータ抽出ループ識別子。 */
+    ParseStage CAMERA_LIST = new ParseStage();
+
+
+    /**
+     * カメラモーションのキーフレーム番号に関する情報を通知する。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param keyFrameNo キーフレーム番号
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraMotion(int keyFrameNo)
+            throws MmdFormatException;
+
+    /**
+     * ターゲットとカメラ間の距離情報を通知する。
+     * <p>球座標(極座標)の動径に相当する。
+     * 通常はターゲットより手前に位置するカメラまでの距離が負の値で渡される。
+     * <p>カメラ位置がターゲットを突き抜けた場合は正の値もとりうる。
+     * ※MMDのUIと符号が逆なので注意。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param range 距離
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraRange(float range)
+            throws MmdFormatException;
+
+    /**
+     * カメラのターゲット位置情報を通知する。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param xPos ターゲットのX座標
+     * @param yPos ターゲットのY座標
+     * @param zPos ターゲットのZ座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraPosition(float xPos, float yPos, float zPos)
+            throws MmdFormatException;
+
+    /**
+     * カメラの回転および回転量情報を通知する。
+     * <p>極座標(球座標)が用いられる。
+     * <p>180度を超える値も回転量として意味を持つ。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param latitude ターゲットから見たカメラの仰俯角(≒緯度)。
+     * 単位はラジアン。
+     * <p>Y軸回転量が0の時のZ正軸がY正軸へ倒れる方向が正回転。
+     * (MMDのUIとは符号が逆になるので注意)
+     * <p>仰俯角が0の場合、
+     * カメラはターゲットに対しXZ平面(水平)と平行な箇所に位置する。
+     * @param longitude Y軸周りの回転量(≒経度)。単位はラジアン。
+     * <p>X正軸がZ正軸へ倒れる方向が正回転。(ボーン回転と逆)
+     * <p>仰俯角およびY軸回転量が0の場合、
+     * カメラレンズはZ軸-∞方向からZ軸+∞方向を向く。
+     * @param roll レンズをターゲットを向けたカメラのロール回転量。
+     * <p>仰俯角とY軸回転量が0の時にY正軸がX正軸に倒れる方向が正回転。
+     * <p>仰俯角およびロール回転量が0の場合、カメラ上部はY軸+∞の方を向く。
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraRotation(float latitude, float longitude, float roll)
+            throws MmdFormatException;
+
+    /**
+     * カメラを通じたスクリーン座標への投影に関する情報を通知する。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param angle 縦画角。単位は度数法。MMDのUIでは1から125が指定可能。
+     * @param hasPerspective パースペクティブスイッチがONならtrue。
+     * スイッチがOFFの場合、画角は無視され遠近感処理が行われなくなる。
+     * (平行投影?)
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraProjection(int angle, boolean hasPerspective)
+            throws MmdFormatException;
+
+    /**
+     * カメラターゲットX軸移動補間情報の通知を受け取る。
+     * 三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param p1x P1点のX座標
+     * @param p1y P1点のY座標
+     * @param p2x P2点のX座標
+     * @param p2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraIntpltXpos(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException;
+
+    /**
+     * カメラターゲットY軸移動補間情報の通知を受け取る。
+     * 三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param p1x P1点のX座標
+     * @param p1y P1点のY座標
+     * @param p2x P2点のX座標
+     * @param p2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraIntpltYpos(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException;
+
+    /**
+     * カメラターゲットZ軸移動補間情報の通知を受け取る。
+     * 三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param p1x P1点のX座標
+     * @param p1y P1点のY座標
+     * @param p2x P2点のX座標
+     * @param p2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraIntpltZpos(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException;
+
+    /**
+     * カメラ回転量補間情報の通知を受け取る。
+     * 三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * <p>カメラ回転でクォータニオン補間は使われない。
+     * @param p1x P1点のX座標
+     * @param p1y P1点のY座標
+     * @param p2x P2点のX座標
+     * @param p2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraIntpltRotation(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException;
+
+    /**
+     * カメラ-ターゲット間距離補間情報の通知を受け取る。
+     * 三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param p1x P1点のX座標
+     * @param p1y P1点のY座標
+     * @param p2x P2点のX座標
+     * @param p2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraIntpltRange(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException;
+
+    /**
+     * スクリーン投影補間情報の通知を受け取る。
+     * 三次ベジェ曲線のP1,P2点に関する情報を受け取る。
+     * <p>{@link #CAMERA_LIST}ループの構成要素。
+     * @param p1x P1点のX座標
+     * @param p1y P1点のY座標
+     * @param p2x P2点のX座標
+     * @param p2y P2点のY座標
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdCameraIntpltProjection(byte p1x, byte p1y, byte p2x, byte p2y)
+            throws MmdFormatException;
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdCameraParser.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdCameraParser.java
new file mode 100644 (file)
index 0000000..7065c7c
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * VMD camera motion parser
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import java.io.IOException;
+import jp.sourceforge.mikutoga.parser.CommonParser;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.MmdSource;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+
+/**
+ * VMDモーションファイルのカメラモーションパーサ。
+ */
+class VmdCameraParser extends CommonParser{
+
+    private static final int BZ_SIZE = 4;           // 4byte Bezier parameter
+    private static final int BZXYZ_SIZE = BZ_SIZE * 3; // XYZ Bezier
+    private static final int BZETC_SIZE = BZ_SIZE * 3; // etc. Bezier
+
+
+    private final byte[] xyzIntplt = new byte[BZXYZ_SIZE];
+    private final byte[] etcIntplt = new byte[BZETC_SIZE];
+
+    private VmdCameraHandler handler = null;
+
+
+    /**
+     * コンストラクタ。
+     * @param source 入力ソース
+     */
+    VmdCameraParser(MmdSource source){
+        super(source);
+        return;
+    }
+
+
+    /**
+     * カメラワーク情報通知用ハンドラを登録する。
+     * @param cameraHandler ハンドラ
+     */
+    void setCameraHandler(VmdCameraHandler cameraHandler){
+        this.handler = cameraHandler;
+        return;
+    }
+
+    /**
+     * カメラモーションデータのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    void parse() throws IOException, MmdFormatException {
+        int cameraMotionNo = parseInteger();
+
+        if(this.handler == null){
+            skip(VmdConst.CAMERA_DATA_SZ * cameraMotionNo);
+            return;
+        }
+
+        this.handler.loopStart(VmdCameraHandler.CAMERA_LIST, cameraMotionNo);
+
+        for(int ct = 0; ct < cameraMotionNo; ct++){
+            int keyFrameNo = parseInteger();
+            this.handler.vmdCameraMotion(keyFrameNo);
+
+            float range = parseFloat();
+            this.handler.vmdCameraRange(range);
+
+            float xPos = parseFloat();
+            float yPos = parseFloat();
+            float zPos = parseFloat();
+            this.handler.vmdCameraPosition(xPos, yPos, zPos);
+
+            float latitude  = parseFloat();
+            float longitude = parseFloat();
+            float roll      = parseFloat();
+            this.handler.vmdCameraRotation(latitude, longitude, roll);
+
+            parseCameraXyzInterpolation();
+            parseCameraEtcInterpolation();
+
+            int angle = parseInteger();
+            boolean hasPerspective = ! parseBoolean();
+            this.handler.vmdCameraProjection(angle, hasPerspective);
+
+            this.handler.loopNext(VmdCameraHandler.CAMERA_LIST);
+        }
+
+        this.handler.loopEnd(VmdCameraHandler.CAMERA_LIST);
+
+        return;
+    }
+
+    /**
+     * カメラターゲット補間データのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseCameraXyzInterpolation()
+            throws IOException, MmdFormatException{
+        if(this.handler == null){
+            skip(this.xyzIntplt.length);
+            return;
+        }
+
+        parseByteArray(this.xyzIntplt);
+
+        int idx = 0;
+
+        byte xP1x = this.xyzIntplt[idx++];
+        byte xP2x = this.xyzIntplt[idx++];
+        byte xP1y = this.xyzIntplt[idx++];
+        byte xP2y = this.xyzIntplt[idx++];
+
+        byte yP1x = this.xyzIntplt[idx++];
+        byte yP2x = this.xyzIntplt[idx++];
+        byte yP1y = this.xyzIntplt[idx++];
+        byte yP2y = this.xyzIntplt[idx++];
+
+        byte zP1x = this.xyzIntplt[idx++];
+        byte zP2x = this.xyzIntplt[idx++];
+        byte zP1y = this.xyzIntplt[idx++];
+        byte zP2y = this.xyzIntplt[idx++];
+
+        assert idx == this.xyzIntplt.length;
+
+        this.handler.vmdCameraIntpltXpos(xP1x, xP1y, xP2x, xP2y);
+        this.handler.vmdCameraIntpltYpos(yP1x, yP1y, yP2x, yP2y);
+        this.handler.vmdCameraIntpltZpos(zP1x, zP1y, zP2x, zP2y);
+
+        return;
+    }
+
+    /**
+     * ターゲット位置以外の補間データのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseCameraEtcInterpolation()
+            throws IOException, MmdFormatException{
+        if(this.handler == null){
+            skip(this.etcIntplt.length);
+            return;
+        }
+
+        parseByteArray(this.etcIntplt);
+
+        int idx = 0;
+
+        byte rP1x = this.etcIntplt[idx++];
+        byte rP2x = this.etcIntplt[idx++];
+        byte rP1y = this.etcIntplt[idx++];
+        byte rP2y = this.etcIntplt[idx++];
+
+        byte dP1x = this.etcIntplt[idx++];
+        byte dP2x = this.etcIntplt[idx++];
+        byte dP1y = this.etcIntplt[idx++];
+        byte dP2y = this.etcIntplt[idx++];
+
+        byte pP1x = this.etcIntplt[idx++];
+        byte pP2x = this.etcIntplt[idx++];
+        byte pP1y = this.etcIntplt[idx++];
+        byte pP2y = this.etcIntplt[idx++];
+
+        assert idx == this.etcIntplt.length;
+
+        this.handler.vmdCameraIntpltRotation  (rP1x, rP1y, rP2x, rP2y);
+        this.handler.vmdCameraIntpltRange     (dP1x, dP1y, dP2x, dP2y);
+        this.handler.vmdCameraIntpltProjection(pP1x, pP1y, pP2x, pP2y);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdLightingHandler.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdLightingHandler.java
new file mode 100644 (file)
index 0000000..f49a36f
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * VMD lighting handler
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import jp.sourceforge.mikutoga.parser.LoopHandler;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.ParseStage;
+
+/**
+ * VMDモーションファイルのライティング情報(照明光源・セルフシャドウ)
+ * の通知用ハンドラ。
+ * <p>フレーム番号は相対的なものとなる。
+ * カメラ・照明・シャドウのうち
+ * 一番若いモーションのフレーム番号が0となる模様。
+ */
+public interface VmdLightingHandler extends LoopHandler {
+
+    /** 照明光源データ抽出ループ識別子。 */
+    ParseStage LUMINOUS_LIST = new ParseStage();
+
+    /** セルフシャドウデータ抽出ループ識別子。 */
+    ParseStage SHADOW_LIST = new ParseStage();
+
+
+    /**
+     * 照明情報のキーフレーム番号に関する情報を通知する。
+     * <p>{@link #LUMINOUS_LIST}ループの構成要素。
+     * @param keyFrameNo キーフレーム番号
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdLuminousMotion(int keyFrameNo)
+            throws MmdFormatException;
+
+    /**
+     * 光源の色情報を通知する。
+     * <p>色情報はRGB色空間で記述される。
+     * <p>MMDのUI上の各色成分指定0~255定義域に便宜上256を追加したものが、
+     * 0.0以上1.0以下にマップされる。
+     * <ul>
+     * <li>0は正しく0.0にマップされる。
+     * <li>128は正しく0.5にマップされる。
+     * <li>255は1.0より少しだけ小さい数にマップされる。
+     * </ul>
+     * <p>{@link #LUMINOUS_LIST}ループの構成要素。
+     * @param rVal 赤成分(0.0以上1.0以下)
+     * @param gVal 緑成分(0.0以上1.0以下)
+     * @param bVal 青成分(0.0以上1.0以下)
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdLuminousColor(float rVal, float gVal, float bVal)
+            throws MmdFormatException;
+
+    /**
+     * 光源の方向情報を通知する。
+     * <p>照明方向は、
+     * ワールド座標原点から伸びる方向ベクトルとして記述される。
+     * この方向ベクトルに向けて、無限遠の光源から照明が当たる。
+     * <p>MMDのスライダUI上では各軸成分の定義域は-1.0以上+1.0以下だが、
+     * さらに絶対値の大きな値を指定することも可能。
+     * <p>方向ベクトルの長さは演出上の意味を持たないが、
+     * キーフレーム間の照明方向の補間に影響を及ぼすかもしれない。
+     * <p>方向ベクトルが零ベクトル(0,0,0)の場合、全ポリゴンに影が落ちる。
+     * <p>{@link #LUMINOUS_LIST}ループの構成要素。
+     * @param xVec 方向ベクトルX軸成分
+     * @param yVec 方向ベクトルY軸成分
+     * @param zVec 方向ベクトルZ軸成分
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdLuminousDirection(float xVec, float yVec, float zVec)
+            throws MmdFormatException;
+
+    /**
+     * シャドウ演出情報のキーフレーム番号に関する情報を通知する。
+     * <p>{@link #SHADOW_LIST}ループの構成要素。
+     * @param keyFrameNo キーフレーム番号
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdShadowMotion(int keyFrameNo)
+            throws MmdFormatException;
+
+    /**
+     * セルフシャドウモードを通知する。
+     * <p>{@link #SHADOW_LIST}ループの構成要素。
+     * @param shadowMode シャドウモード指定。
+     * <ul>
+     * <li>0 : シャドウOFF
+     * <li>1 : mode1 影描画の質がカメラからの距離の影響をあまり受けない。
+     * <li>2 : mode2 影描画の質をカメラからの距離に応じて劣化させる
+     * ことにより、カメラに近いオブジェクトの影描画の質を向上させる。
+     * </ul>
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdShadowMode(byte shadowMode)
+            throws MmdFormatException;
+
+    /**
+     * セルフシャドウの描画対象となるオブジェクトの範囲(カメラからの距離)
+     * を通知する。
+     * <p>通知されるのは幾何的な距離ではない。
+     * MMDのUI値(カメラからの距離の100倍?)を
+     * 10万で割った商を0.1から引いた値が通知される。
+     * <p>{@link #SHADOW_LIST}ループの構成要素。
+     * @param shadowScope 距離情報。
+     * @throws MmdFormatException 不正フォーマットによる
+     * パース処理の中断をパーサに指示
+     */
+    void vmdShadowScopeRaw(float shadowScope)
+            throws MmdFormatException;
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdLightingParser.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdLightingParser.java
new file mode 100644 (file)
index 0000000..3786c71
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * VMD lighting data parser
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import java.io.IOException;
+import jp.sourceforge.mikutoga.parser.CommonParser;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.MmdSource;
+import jp.sourceforge.mikutoga.vmd.VmdConst;
+
+/**
+ * VMDモーションファイルのライティング情報パーサ。
+ * 照明光源演出データと影演出データを含む。
+ * <p>古い版のVMDファイルには影演出データが記述されていない場合がある。
+ */
+class VmdLightingParser extends CommonParser {
+
+    private VmdLightingHandler handler = null;
+
+
+    /**
+     * コンストラクタ。
+     * @param source 入力ソース
+     */
+    VmdLightingParser(MmdSource source){
+        super(source);
+        return;
+    }
+
+
+    /**
+     * ライティング情報通知用ハンドラを登録する。
+     * @param lightingHandler ハンドラ
+     */
+    void setLightingHandler(VmdLightingHandler lightingHandler){
+        this.handler = lightingHandler;
+        return;
+    }
+
+    /**
+     * ライティングデータのパースと通知。
+     * <p>影演出データが無ければ読みに行かない。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    void parse() throws IOException, MmdFormatException {
+        parseVmdLighting();
+        if(hasMore()){
+            parseVmdShadow();
+        }
+        return;
+    }
+
+    /**
+     * 光源モーションデータのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdLighting() throws IOException, MmdFormatException{
+        int lightMotionNo = parseInteger();
+
+        if(this.handler == null){
+            skip(VmdConst.LUMINOUS_DATA_SZ * lightMotionNo);
+            return;
+        }
+
+        this.handler.loopStart(VmdLightingHandler.LUMINOUS_LIST,
+                lightMotionNo);
+
+        for(int ct = 0; ct < lightMotionNo; ct++){
+            int keyFrameNo = parseInteger();
+            this.handler.vmdLuminousMotion(keyFrameNo);
+
+            float rVal = parseFloat();
+            float gVal = parseFloat();
+            float bVal = parseFloat();
+            this.handler.vmdLuminousColor(rVal, gVal, bVal);
+
+            float xVec = parseFloat();
+            float yVec = parseFloat();
+            float zVec = parseFloat();
+            this.handler.vmdLuminousDirection(xVec, yVec, zVec);
+
+            this.handler.loopNext(VmdLightingHandler.LUMINOUS_LIST);
+        }
+
+        this.handler.loopEnd(VmdLightingHandler.LUMINOUS_LIST);
+
+        return;
+    }
+
+    /**
+     * 影演出データのパースと通知。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseVmdShadow() throws IOException, MmdFormatException{
+        int shadowMotionNo = parseInteger();
+
+        if(this.handler == null){
+            skip(VmdConst.SHADOW_DATA_SZ * shadowMotionNo);
+            return;
+        }
+
+        this.handler.loopStart(VmdLightingHandler.SHADOW_LIST,
+                shadowMotionNo);
+
+        for(int ct = 0; ct < shadowMotionNo; ct++){
+            int keyFrameNo = parseInteger();
+            this.handler.vmdShadowMotion(keyFrameNo);
+
+            byte shadowMode = parseByte();
+            this.handler.vmdShadowMode(shadowMode);
+
+            float shadowScope = parseFloat();
+            this.handler.vmdShadowScopeRaw(shadowScope);
+
+            this.handler.loopNext(VmdLightingHandler.SHADOW_LIST);
+        }
+
+        this.handler.loopEnd(VmdLightingHandler.SHADOW_LIST);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdParser.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/VmdParser.java
new file mode 100644 (file)
index 0000000..95fb1df
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * VMD file parser
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+import java.io.IOException;
+import jp.sourceforge.mikutoga.parser.MmdFormatException;
+import jp.sourceforge.mikutoga.parser.MmdSource;
+
+/**
+ * VMDモーションファイルのパーサ。
+ */
+public class VmdParser {
+
+    private final MmdSource source;
+
+    private final VmdBasicParser    basicParser;
+    private final VmdCameraParser   cameraParser;
+    private final VmdLightingParser lightingParser;
+
+    private VmdBasicHandler basicHandler  = null;
+    private boolean strictMode = true;
+
+
+    /**
+     * コンストラクタ。
+     * @param source 入力ソース
+     * @throws NullPointerException 引数がnull
+     */
+    public VmdParser(MmdSource source) throws NullPointerException{
+        super();
+
+        if(source == null) throw new NullPointerException();
+        this.source = source;
+
+        this.basicParser    = new VmdBasicParser(source);
+        this.cameraParser   = new VmdCameraParser(source);
+        this.lightingParser = new VmdLightingParser(source);
+
+        return;
+    }
+
+
+    /**
+     * 入力ソースを返す。
+     * @return 入力ソース
+     */
+    public MmdSource getSource(){
+        return this.source;
+    }
+
+    /**
+     * 基本情報通知用ハンドラを登録する。
+     * @param handler ハンドラ
+     */
+    public void setBasicHandler(VmdBasicHandler handler){
+        this.basicParser.setBasicHandler(handler);
+        this.basicHandler = handler;
+        return;
+    }
+
+    /**
+     * カメラワーク情報通知用ハンドラを登録する。
+     * @param cameraHandler ハンドラ
+     */
+    public void setCameraHandler(VmdCameraHandler cameraHandler){
+        this.cameraParser.setCameraHandler(cameraHandler);
+        return;
+    }
+
+    /**
+     * ライティング情報通知用ハンドラを登録する。
+     * @param lightingHandler ハンドラ
+     */
+    public void setLightingHandler(VmdLightingHandler lightingHandler){
+        this.lightingParser.setLightingHandler(lightingHandler);
+        return;
+    }
+
+    /**
+     * 厳密なパース(Strict-mode)を行うか否か設定する。
+     * デフォルトではStrict-modeはオン。
+     * <p>Strict-mode下では、
+     * ボーンモーションの冗長な補間情報の一貫性チェックが行われ、
+     * モデル名がなんであろうとカメラ・ライティングデータのパースを試みる。
+     * <p>※MMDVer7.30前後のVMD出力不具合を回避したい場合は、
+     * Strict-modeをオフにするとパースに成功する場合がある。
+     * @param mode Strict-modeに設定したければtrue
+     */
+    public void setStrictMode(boolean mode){
+        this.strictMode = mode;
+        this.basicParser.setStrictMode(this.strictMode);
+        return;
+    }
+
+    /**
+     * VMDファイルのパースを開始する。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    public void parseVmd() throws IOException, MmdFormatException {
+        if(this.basicHandler != null){
+            this.basicHandler.vmdParseStart();
+        }
+
+        parseBody();
+
+        boolean hasMoreData = this.source.hasMore();
+        if(this.basicHandler != null){
+            this.basicHandler.vmdParseEnd(hasMoreData);
+        }
+
+        return;
+    }
+
+    /**
+     * VMDファイル本体のパースを開始する。
+     * <p>モデル名がボーンモーション用と推測され、
+     * かつパーサがStrict-modeでない場合、
+     * カメラ、ライティングデータのパースは行われない。
+     * @throws IOException IOエラー
+     * @throws MmdFormatException フォーマットエラー
+     */
+    private void parseBody() throws IOException, MmdFormatException{
+        this.basicParser.parse();
+
+        if(this.basicParser.hasStageActName() || this.strictMode){
+            this.cameraParser.parse();
+            this.lightingParser.parse();
+        }
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/mikutoga/vmd/parser/package-info.java b/src/main/java/jp/sourceforge/mikutoga/vmd/parser/package-info.java
new file mode 100644 (file)
index 0000000..5f3488b
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * package information for Javadoc
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 MikuToga Partners
+ */
+
+/**
+ * MikuMikuDanceのVMDモーションファイル(*.vmd)用パーサライブラリ。
+ * <p>コールバックインタフェースを実装したオブジェクトをパーサに渡し、
+ * VMDモーションファイルのパースを開始すると、
+ * 各種データの出現に応じてコールバックメソッドを次々と呼び出してくれる。
+ */
+
+package jp.sourceforge.mikutoga.vmd.parser;
+
+/* EOF */