Exemplo n.º 1
0
        public void Execute()
        {
            // KMR モジュールで全ファイル分のList<Keypoint>(平滑化済み)を取ってくる(今はまだ1フレーム分)
            var opr = new OpenPoseReader(@"C:\tmp\");

            _keypointsCollection.Add(1, opr.ReadOneFile("Sample_000000000213_keypoints.json"));

            // foreachで1つずつSZKモジュールで処理
            var dolls = new Dictionary <int, Doll>();

            foreach (var keypoints in _keypointsCollection)
            {
                var estimator = new PoseEstimator();
                dolls.Add(keypoints.Key, estimator.EstimateSingleFrame(1920, 1080, keypoints.Value));
            }

            var vmd = new Vmd("Sample", dolls);
            var vw  = new VmdWriter(@"C:\tmp\Sample_000000000213_keypoints.vmd");

            vw.Write(vmd);

            // ※必要な入力引数
            // OpenPoseディレクトリ(平面座標取得から一貫して行うなら)
            // OpenPose出力先ディレクトリ
            // Vmd出力ファイルパス
        }
Exemplo n.º 2
0
        static void Main(string[] args)
        {
#if DEBUG
            string path = @"C:\tmp\Sample_000000000213_keypoints.vmd";
#else
            string path = args[0];
#endif

            var vr  = new VmdReader(path);
            var vmd = vr.Read();

            var vw = new VmdWriter(path.Substring(0, path.LastIndexOf('.')) + "_rewrite.vmd");
            vw.Write(vmd);

            foreach (var frame in vmd.Frames)
            {
                Console.WriteLine($"Frame #{frame.Index}");
                Console.WriteLine($"-----------------------------------------");

                foreach (var bone in frame.Bones)
                {
                    Console.WriteLine($"Bone {bone.Key}:");

                    var p = bone.Value.Position;
                    Console.WriteLine($"  Position     (x,y,z)   = ({p.X}, {p.Y}, {p.Z})");
                    var q = bone.Value.Quaternion;
                    Console.WriteLine($"  Quartenion   (x,y,z,w) = ({q.X}, {q.Y}, {q.Z}, {q.W})");
                    var e = EulerAngle.FromQuaternion(q);
                    Console.WriteLine($"  Euler angles (h,p,b)   = ({e.HeadingDeg}, {e.PitchDeg}, {e.BankDeg})");
                }
                Console.WriteLine($"");
            }

#if DEBUG
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);
#else
#endif
        }
Exemplo n.º 3
0
        private void DoWorkInternal([NotNull] object state)
        {
            Debug.Assert(InvokeRequired, "The worker procedure should be running on the worker thread.");

            Log("Worker started.");

            var p = (MainWorkerInputParams)state;

            var conversionConfig = PrepareConversionConfig(p);
            var scalingConfig    = new ScalingConfig(conversionConfig);

            if (p.IdolHeight <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(p.IdolHeight), p.IdolHeight, "Invalid idol height.");
            }

            scalingConfig.CharacterHeight = p.IdolHeight;

            do
            {
                TransformHierarchies combinedHierarchies; // TODO: Do we still need this?
                CompositeAvatar      combinedAvatar;
                CompositeMesh        combinedMesh;
                int            bodyMeshVertexCount;
                SwayController headSway;
                SwayController bodySway;

                if (p.GenerateModel || p.GenerateCharacterMotion)
                {
                    Log("Loading body avatar...");
                    var bodyAvatar = ResourceLoader.LoadBodyAvatar(p.InputBody);
                    if (bodyAvatar == null)
                    {
                        Log("Cannot load body avatar.");
                        break;
                    }

                    Log("Loading body object hierarchies...");
                    var bodyHierarchies = ResourceLoader.LoadTransformHierarchies(p.InputBody);

                    Log("Loading body mesh...");
                    var bodyMesh = ResourceLoader.LoadBodyMesh(p.InputBody);
                    if (bodyMesh == null)
                    {
                        Log("Cannot load body mesh.");
                        break;
                    }

                    Log("Loading head avatar...");
                    var headAvatar = ResourceLoader.LoadHeadAvatar(p.InputHead);
                    if (headAvatar == null)
                    {
                        Log("Cannot load head avatar.");
                        break;
                    }

                    Log("Loading head object hierarchies...");
                    var headHierarchies = ResourceLoader.LoadTransformHierarchies(p.InputHead);

                    Log("Loading head mesh...");
                    var headMesh = ResourceLoader.LoadHeadMesh(p.InputHead);
                    if (headMesh == null)
                    {
                        Log("Cannot load head mesh.");
                        break;
                    }

                    Log("Loading head/body sway controllers...");
                    (bodySway, headSway) = ResourceLoader.LoadSwayControllers(p.InputBody, p.InputHead);
                    if (bodySway == null || headSway == null)
                    {
                        Log("Cannot load sway controllers.");
                        break;
                    }

                    Log("Combining avatars and meshes...");
                    combinedHierarchies = TransformHierarchies.Combine(bodyHierarchies, headHierarchies);
                    combinedHierarchies = combinedHierarchies.PreserveMltdSpecificOnly();
                    combinedAvatar      = CompositeAvatar.FromAvatars(bodyAvatar, headAvatar);
                    combinedMesh        = CompositeMesh.FromMeshes(bodyMesh, headMesh);
                    bodyMeshVertexCount = bodyMesh.VertexCount;
                }
                else
                {
                    combinedHierarchies = null;
                    combinedAvatar      = null;
                    combinedMesh        = null;
                    bodyMeshVertexCount = 0;
                    bodySway            = null;
                    headSway            = null;
                }

                IBodyAnimationSource mainDance;
                IBodyAnimationSource danceAppeal = null;

                if (p.GenerateCharacterMotion)
                {
                    Log("Loading dance motion...");
                    var loadedDance = ResourceLoader.LoadDance(p.InputDance, p.MotionNumber, p.FormationNumber);
                    IBodyAnimationSource apSpecial, apAnother, apGorgeous;
                    (mainDance, apSpecial, apAnother, apGorgeous) = (loadedDance.Default, loadedDance.Special, loadedDance.Another, loadedDance.Gorgeous);
                    if (mainDance == null)
                    {
                        if (MltdAnimation.MinDance <= loadedDance.SuggestedPosition && loadedDance.SuggestedPosition <= MltdAnimation.MaxDance)
                        {
                            Log($"Cannot load dance data. However, this file may contain animation for idol using motion {loadedDance.SuggestedPosition.ToString()}. Please check whether you selected the correct motion number (in 'Motions' tab).");
                        }
                        else
                        {
                            Log("Cannot load dance data. Please check whether you selected a dance data file.");
                        }

                        break;
                    }

                    if (p.AppealType != AppealType.None)
                    {
                        Log($"Trying to load dance appeal: {p.AppealType}");

                        switch (p.AppealType)
                        {
                        case AppealType.Special: {
                            if (apSpecial != null)
                            {
                                danceAppeal = apSpecial;
                            }

                            break;
                        }

                        case AppealType.Another: {
                            if (apAnother != null)
                            {
                                danceAppeal = apAnother;
                            }

                            break;
                        }

                        case AppealType.Gorgeous: {
                            if (apGorgeous != null)
                            {
                                danceAppeal = apGorgeous;
                            }

                            break;
                        }

                        default:
                            throw new ArgumentOutOfRangeException();
                        }

                        if (danceAppeal == null)
                        {
                            Log($"Selected dance appeal for idol {p.FormationNumber.ToString()} is not found in the main dance data file. Trying with external file.");

                            if (string.IsNullOrWhiteSpace(p.ExternalDanceAppealFile))
                            {
                                Log("External dance appeal file is empty. Please set the path to the file.");
                                break;
                            }

                            var externalAppealData = ResourceLoader.LoadDance(p.ExternalDanceAppealFile, p.MotionNumber, p.FormationNumber);
                            (apSpecial, apAnother, apGorgeous) = (externalAppealData.Special, externalAppealData.Another, externalAppealData.Gorgeous);

                            switch (p.AppealType)
                            {
                            case AppealType.Special: {
                                if (apSpecial != null)
                                {
                                    danceAppeal = apSpecial;
                                }

                                break;
                            }

                            case AppealType.Another: {
                                if (apAnother != null)
                                {
                                    danceAppeal = apAnother;
                                }

                                break;
                            }

                            case AppealType.Gorgeous: {
                                if (apGorgeous != null)
                                {
                                    danceAppeal = apGorgeous;
                                }

                                break;
                            }

                            default:
                                throw new ArgumentOutOfRangeException();
                            }

                            if (danceAppeal == null)
                            {
                                Log("Cannot load dance appeal data. Possible causes: 1) the file is not an appeal data file; 2) this song does not have the appeal you selected; 3) there is no corresponding appeal for selected position (e.g. each appeal of クルリウタ [Kururi Uta] only matches 3 out of 5 characters). Using default option (no appeal).");
                            }
                        }
                    }
                }
                else
                {
                    mainDance   = null;
                    danceAppeal = null;
                }

                ScenarioObject baseScenario, landscapeScenario, portraitScenario;
                ScenarioObject formationInfo;

                if (p.GenerateCharacterMotion || p.GenerateLipSync || p.GenerateFacialExpressions || p.GenerateCameraMotion)
                {
                    (baseScenario, landscapeScenario, portraitScenario) = ResourceLoader.LoadScenario(p.InputScenario);
                    if (baseScenario == null)
                    {
                        Log("Cannot load base scenario object.");
                        break;
                    }

                    if (landscapeScenario == null)
                    {
                        Log("Cannot load landscape scenario.");
                        break;
                    }

                    if (portraitScenario == null)
                    {
                        Log("Cannot load portrait scenario.");
                        break;
                    }
                }
                else
                {
                    baseScenario      = null;
                    landscapeScenario = null;
                    portraitScenario  = null;
                }

                if (p.GenerateCharacterMotion)
                {
                    Debug.Assert(baseScenario != null, nameof(baseScenario) + " != null");
                    Debug.Assert(landscapeScenario != null, nameof(landscapeScenario) + " != null");
                    Debug.Assert(portraitScenario != null, nameof(portraitScenario) + " != null");

                    if (baseScenario.HasFormationChangeEvents())
                    {
                        formationInfo = baseScenario;
                    }
                    else
                    {
                        Log("Main scenario object does not contain facial expressions. Trying with landscape and portrait.");

                        if (landscapeScenario.HasFormationChangeEvents())
                        {
                            Log("Using formation info from landscape.");
                            formationInfo = landscapeScenario;
                        }
                        else if (portraitScenario.HasFormationChangeEvents())
                        {
                            Log("Using formation info from portrait.");
                            formationInfo = portraitScenario;
                        }
                        else
                        {
                            Log("No scenario object contains formation info.");
                            break;
                        }
                    }
                }
                else
                {
                    formationInfo = null;
                }

                ScenarioObject lipSyncInfo, facialExprInfo;

                if (p.GenerateLipSync || p.GenerateFacialExpressions)
                {
                    Debug.Assert(baseScenario != null, nameof(baseScenario) + " != null");
                    Debug.Assert(landscapeScenario != null, nameof(landscapeScenario) + " != null");
                    Debug.Assert(portraitScenario != null, nameof(portraitScenario) + " != null");

                    if (p.GenerateLipSync)
                    {
                        lipSyncInfo = baseScenario;
                    }
                    else
                    {
                        lipSyncInfo = null;
                    }

                    if (p.GenerateFacialExpressions)
                    {
                        if (baseScenario.HasFacialExpressionEvents())
                        {
                            facialExprInfo = baseScenario;
                        }
                        else
                        {
                            Log("Main scenario object does not contain facial expressions. Trying with landscape and portrait.");

                            var foundFacialExpr = true;

                            switch (p.PreferredFacialExpressionSource)
                            {
                            case MainWorkerInputParams.FallbackFacialExpressionSource.Landscape: {
                                if (landscapeScenario.HasFacialExpressionEvents())
                                {
                                    facialExprInfo = landscapeScenario;
                                    Log("Using facial expressions: landscape.");
                                }
                                else if (portraitScenario.HasFacialExpressionEvents())
                                {
                                    Log("Facial expressions are not found in landscape, but found in portrait. Use portrait instead.");
                                    facialExprInfo = portraitScenario;
                                }
                                else
                                {
                                    Log("No scenario object contains facial expressions.");
                                    facialExprInfo  = null;
                                    foundFacialExpr = false;
                                }

                                break;
                            }

                            case MainWorkerInputParams.FallbackFacialExpressionSource.Portrait:
                                if (portraitScenario.HasFacialExpressionEvents())
                                {
                                    facialExprInfo = portraitScenario;
                                    Log("Using facial expressions: portrait.");
                                }
                                else if (landscapeScenario.HasFacialExpressionEvents())
                                {
                                    Log("Facial expressions are not found in portrait, but found in landscape. Use landscape instead.");
                                    facialExprInfo = landscapeScenario;
                                }
                                else
                                {
                                    Log("No scenario object contains facial expressions.");
                                    facialExprInfo  = null;
                                    foundFacialExpr = false;
                                }

                                break;

                            default:
                                throw new ArgumentOutOfRangeException(nameof(p.PreferredFacialExpressionSource), p.PreferredFacialExpressionSource, "Invalid facial expression source.");
                            }

                            if (!foundFacialExpr)
                            {
                                break;
                            }
                        }
                    }
                    else
                    {
                        facialExprInfo = null;
                    }
                }
                else
                {
                    lipSyncInfo    = null;
                    facialExprInfo = null;
                }

                CharacterImasMotionAsset mainCamera;
                CharacterImasMotionAsset cameraAppeal = null;

                if (p.GenerateCameraMotion)
                {
                    Log("Loading camera motion...");
                    CharacterImasMotionAsset apSpecial, apAnother, apGorgeous;
                    var loadedCamera = ResourceLoader.LoadCamera(p.InputCamera, p.DesiredCameraNumber);
                    (mainCamera, apSpecial, apAnother, apGorgeous) = (loadedCamera.Default, loadedCamera.Special, loadedCamera.Another, loadedCamera.Gorgeous);
                    if (mainCamera == null)
                    {
                        Log("Cannot load camera data.");
                        break;
                    }

                    if (p.DesiredCameraNumber.HasValue && loadedCamera.CameraNumber == ResourceLoader.InvalidCameraNumber)
                    {
                        Log($"Cannot find motion for camera #{p.DesiredCameraNumber.Value.ToString()}, using the first found motion.");
                    }

                    if (p.AppealType != AppealType.None)
                    {
                        Log($"Trying to load appeal for camera: {p.AppealType}");

                        switch (p.AppealType)
                        {
                        case AppealType.Special: {
                            if (apSpecial != null)
                            {
                                cameraAppeal = apSpecial;
                            }

                            break;
                        }

                        case AppealType.Another: {
                            if (apAnother != null)
                            {
                                cameraAppeal = apAnother;
                            }

                            break;
                        }

                        case AppealType.Gorgeous: {
                            if (apGorgeous != null)
                            {
                                cameraAppeal = apGorgeous;
                            }

                            break;
                        }

                        default:
                            throw new ArgumentOutOfRangeException();
                        }

                        // Otherwise not possible; camera appeals are always stored in the main camera file
                        if (cameraAppeal == null)
                        {
                            Log("Cannot load camera appeal data. Please check whether this song actually has the appeal you selected.");
                            break;
                        }
                    }
                }
                else
                {
                    mainCamera = null;
                }

                // And now the job starts!
                {
                    PmxModel        pmx;
                    string          texPrefix;
                    BakedMaterial[] materialList;

                    if (p.GenerateModel || p.GenerateCharacterMotion)
                    {
                        // Now file names are like "ch_pr001_201xxx.unity3d".
                        var avatarName = (new FileInfo(p.InputHead).Name).Substring(3, 12);

                        // ss001_015siz -> 015ss001
                        // Note: PMD allows max 19 characters in texture file names.
                        // In the format below, textures will be named like:
                        // tex\015ss001_01.png
                        // which is at the limit.
                        texPrefix = avatarName.Substring(6, 3) + avatarName.Substring(0, 5);
                        // TODO: PMX seems to store path in this way. If so, MillionDance only works on Windows.
                        texPrefix = $@"tex\{texPrefix}_";

                        Log("Generating model...");

                        Debug.Assert(combinedHierarchies != null);
                        Debug.Assert(combinedMesh != null);

                        var boneLookup = new BoneLookup(conversionConfig);

                        var pmxCreator           = new PmxCreator(conversionConfig, scalingConfig, boneLookup);
                        var pmxConversionDetails = new PmxCreator.ConversionDetails(texPrefix, p.ApplyGameStyledToon, p.SkinToonNumber, p.ClothesToonNumber);

                        pmx = pmxCreator.CreateModel(combinedAvatar, combinedMesh, bodyMeshVertexCount, bodySway, headSway, pmxConversionDetails, out materialList);
                    }
                    else
                    {
                        pmx          = null;
                        texPrefix    = null;
                        materialList = null;
                    }

                    if (p.GenerateModel)
                    {
                        Log("Saving model...");

                        Debug.Assert(pmx != null);

                        using (var w = new PmxWriter(File.Open(p.OutputModel, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                            w.Write(pmx);
                        }

                        Log("Saving textures...");

                        var modelDir = (new FileInfo(p.OutputModel)).DirectoryName;
                        Debug.Assert(modelDir != null);

                        {
                            var texDir = (new FileInfo(Path.Combine(modelDir, texPrefix))).DirectoryName;
                            Debug.Assert(texDir != null);

                            if (!Directory.Exists(texDir))
                            {
                                Directory.CreateDirectory(texDir);
                            }
                        }

                        foreach (var material in materialList)
                        {
                            var textureFilePath = Path.Combine(modelDir, material.TextureName);

                            using (var memoryStream = new MemoryStream()) {
                                material.BakedTexture.Save(memoryStream, ImageFormat.Png);

                                using (var fileStream = File.Open(textureFilePath, FileMode.Create, FileAccess.Write, FileShare.Write)) {
                                    memoryStream.Position = 0;
                                    memoryStream.CopyTo(fileStream);
                                }
                            }
                        }

                        foreach (var material in materialList)
                        {
                            material.Dispose();
                        }
                    }

                    if (p.GenerateCharacterMotion)
                    {
                        Log("Generating character motion...");

                        var creator = new VmdCreator(conversionConfig, scalingConfig)
                        {
                            ProcessBoneFrames   = true,
                            ProcessCameraFrames = false,
                            ProcessFacialFrames = false,
                            ProcessLightFrames  = false
                        };

                        var danceVmd = creator.CreateDanceMotion(mainDance, baseScenario, formationInfo, combinedAvatar, pmx, danceAppeal, p.FormationNumber, p.AppealType);

                        Log("Saving character motion...");

                        using (var w = new VmdWriter(File.Open(p.OutputCharacterAnimation, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                            w.Write(danceVmd);
                        }
                    }

                    if (p.GenerateLipSync)
                    {
                        Log("Generating lip sync...");

                        var creator = new VmdCreator(conversionConfig, scalingConfig)
                        {
                            ProcessBoneFrames   = false,
                            ProcessCameraFrames = false,
                            ProcessFacialFrames = true,
                            ProcessLightFrames  = false
                        };

                        var lipVmd = creator.CreateLipSync(lipSyncInfo, p.FormationNumber, p.IgnoreSingControl);

                        Log("Saving lip sync...");

                        using (var w = new VmdWriter(File.Open(p.OutputLipSync, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                            w.Write(lipVmd);
                        }
                    }

                    if (p.GenerateFacialExpressions)
                    {
                        Log("Generating facial expressions...");

                        var creator = new VmdCreator(conversionConfig, scalingConfig)
                        {
                            ProcessBoneFrames   = false,
                            ProcessCameraFrames = false,
                            ProcessFacialFrames = true,
                            ProcessLightFrames  = false
                        };

                        var facialVmd = creator.CreateFacialExpressions(facialExprInfo, p.FormationNumber);

                        Log("Saving facial expressions...");

                        using (var w = new VmdWriter(File.Open(p.OutputFacialExpressions, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                            w.Write(facialVmd);
                        }
                    }

                    if (p.GenerateCameraMotion)
                    {
                        Log("Generating camera motion...");

                        if (p.UseMvdForCamera)
                        {
                            var creator = new MvdCreator(conversionConfig, scalingConfig)
                            {
                                ProcessBoneFrames   = false,
                                ProcessCameraFrames = true,
                                ProcessFacialFrames = false,
                                ProcessLightFrames  = false,
                            };

                            var motion = creator.CreateCameraMotion(mainCamera, baseScenario, cameraAppeal, p.AppealType);

                            Log("Writing camera motion...");

                            using (var w = new MvdWriter(File.Open(p.OutputCamera, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                                w.Write(motion);
                            }
                        }
                        else
                        {
                            var creator = new VmdCreator(conversionConfig, scalingConfig)
                            {
                                ProcessBoneFrames   = false,
                                ProcessCameraFrames = true,
                                ProcessFacialFrames = false,
                                ProcessLightFrames  = false,
                            };

                            creator.FixedFov = p.FixedFov;

                            var motion = creator.CreateCameraMotion(mainCamera, baseScenario, cameraAppeal, p.AppealType);

                            Log("Writing camera motion...");

                            using (var w = new VmdWriter(File.Open(p.OutputCamera, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                                w.Write(motion);
                            }
                        }
                    }
                }
            } while (false);
        }
Exemplo n.º 4
0
        private void DoWork(object state)
        {
            ConversionConfig PrepareConversionConfig(InputParams ip)
            {
                var cc = new ConversionConfig();

                cc.MotionFormat                        = ip.MotionSource;
                cc.ScaleToPmxSize                      = ip.ScalePmx;
                cc.ApplyPmxCharacterHeight             = ip.ConsiderIdolHeight;
                cc.TranslateBoneNamesToMmd             = ip.TranslateBoneNames;
                cc.AppendIKBones                       = ip.AppendLegIkBones;
                cc.FixMmdCenterBones                   = ip.FixCenterBones;
                cc.FixTdaBindingPose                   = ip.ConvertBindingPose;
                cc.AppendEyeBones                      = ip.AppendEyeBones;
                cc.HideUnityGeneratedBones             = ip.HideUnityGeneratedBones;
                cc.SkeletonFormat                      = ip.MotionSource == MotionFormat.Mltd ? SkeletonFormat.Mltd : SkeletonFormat.Mmd;
                cc.TranslateFacialExpressionNamesToMmd = ip.TranslateFacialExpressionNames;
                cc.ImportPhysics                       = ip.ImportPhysics;

                cc.Transform60FpsTo30Fps = ip.TransformTo30Fps;
                cc.ScaleToVmdSize        = ip.ScaleVmd;

                {
                    var mappingsJson = File.ReadAllText(ip.FacialExpressionMappingFilePath, Encoding.UTF8);
                    var mappingsObj  = JsonConvert.DeserializeObject <FacialConfig>(mappingsJson);

                    var dict = new Dictionary <int, IReadOnlyDictionary <string, float> >();

                    foreach (var expr in mappingsObj.Expressions)
                    {
                        var d2 = new Dictionary <string, float>();

                        foreach (var kv in expr.Data)
                        {
                            d2[kv.Key] = kv.Value;
                        }

                        dict[expr.Key] = d2;
                    }

                    cc.FacialExpressionMappings = dict;
                }

                return(cc);
            }

            Debug.Assert(InvokeRequired, "The worker procedure should be running on the worker thread.");

            Log("Worker started.");

            try {
                var p = (InputParams)state;

                ConversionConfig.Current = PrepareConversionConfig(p);

                if (p.IdolHeight <= 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(p.IdolHeight), "Invalid idol height.");
                }

                ScalingConfig.CharacterHeight = p.IdolHeight;

                do
                {
                    var bodyAvatar = ResourceLoader.LoadBodyAvatar(p.InputBody);
                    if (bodyAvatar == null)
                    {
                        Log("Failed to load body avatar.");
                        break;
                    }

                    var bodyMesh = ResourceLoader.LoadBodyMesh(p.InputBody);
                    if (bodyMesh == null)
                    {
                        Log("Failed to load body mesh.");
                        break;
                    }

                    var headAvatar = ResourceLoader.LoadHeadAvatar(p.InputHead);
                    if (headAvatar == null)
                    {
                        Log("Failed to load head avatar.");
                        break;
                    }

                    var headMesh = ResourceLoader.LoadHeadMesh(p.InputHead);
                    if (headMesh == null)
                    {
                        Log("Failed to load head mesh.");
                        break;
                    }

                    var(bodySway, headSway) = ResourceLoader.LoadSwayControllers(p.InputBody, p.InputHead);
                    if (bodySway == null || headSway == null)
                    {
                        Log("Failed to load sway controllers.");
                        break;
                    }

                    var combinedAvatar = CompositeAvatar.FromAvatars(bodyAvatar, headAvatar);
                    var combinedMesh   = CompositeMesh.FromMeshes(bodyMesh, headMesh);

                    CharacterImasMotionAsset dance;
                    ScenarioObject           scenario;

                    if (p.GenerateCharacterMotion)
                    {
                        (dance, _, _) = ResourceLoader.LoadDance(p.InputDance, p.SongPosition);
                        if (dance == null)
                        {
                            Log("Failed to load dance data.");
                            break;
                        }

                        scenario = ResourceLoader.LoadScenario(p.InputFacialExpression);
                        if (scenario == null)
                        {
                            Log("Failed to load scenario object.");
                            break;
                        }
                    }
                    else
                    {
                        dance    = null;
                        scenario = null;
                    }

                    CharacterImasMotionAsset camera;

                    if (p.GenerateCameraMotion)
                    {
                        camera = ResourceLoader.LoadCamera(p.InputCamera);
                        if (camera == null)
                        {
                            Log("Failed to load camera data.");
                            break;
                        }
                    }
                    else
                    {
                        camera = null;
                    }

                    do
                    {
                        // Now file names are like "ch_pr001_201xxx.unity3d".
                        var avatarName = (new FileInfo(p.InputHead).Name).Substring(3, 12);

                        // ss001_015siz -> 015ss001
                        // Note: PMD allows max 19 characters in texture file names.
                        // In the format below, textures will be named like:
                        // tex\015ss001_01.png
                        // which is at the limit.
                        var texPrefix = avatarName.Substring(6, 3) + avatarName.Substring(0, 5);
                        texPrefix = @"tex\" + texPrefix + "_";

                        Log("Generating model...");

                        var pmxCreator = new PmxCreator();
                        var pmx        = pmxCreator.CreateFrom(combinedAvatar, combinedMesh, bodyMesh.VertexCount, texPrefix, bodySway, headSway);

                        if (p.GenerateModel)
                        {
                            Log("Saving model...");

                            using (var w = new PmxWriter(File.Open(p.OutputModel, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                                w.Write(pmx);
                            }
                        }

                        if (p.GenerateCharacterMotion)
                        {
                            Log("Generating character motion...");

                            var creator = new VmdCreator {
                                ProcessBoneFrames   = true,
                                ProcessCameraFrames = false,
                                ProcessFacialFrames = true,
                                ProcessLightFrames  = false
                            };

                            var danceVmd = creator.CreateFrom(dance, combinedAvatar, pmx, null, scenario, p.SongPosition);

                            Log("Saving character motion...");

                            using (var w = new VmdWriter(File.Open(p.OutputCharacterAnimation, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                                w.Write(danceVmd);
                            }
                        }

                        if (p.GenerateCameraMotion)
                        {
                            Log("Generating camera motion...");

                            if (p.UseMvd)
                            {
                                var creator = new MvdCreator {
                                    ProcessBoneFrames   = false,
                                    ProcessCameraFrames = true,
                                    ProcessFacialFrames = false,
                                    ProcessLightFrames  = false
                                };

                                var motion = creator.CreateFrom(null, null, null, camera, null, p.SongPosition);

                                Log("Writing camera motion...");

                                using (var w = new MvdWriter(File.Open(p.OutputCamera, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                                    w.Write(motion);
                                }
                            }
                            else
                            {
                                var creator = new VmdCreator {
                                    ProcessBoneFrames   = false,
                                    ProcessCameraFrames = true,
                                    ProcessFacialFrames = false,
                                    ProcessLightFrames  = false
                                };

                                var motion = creator.CreateFrom(null, null, null, camera, null, p.SongPosition);

                                Log("Writing camera motion...");

                                using (var w = new VmdWriter(File.Open(p.OutputCamera, FileMode.Create, FileAccess.Write, FileShare.Write))) {
                                    w.Write(motion);
                                }
                            }
                        }
                    } while (false);
                } while (false);
            } catch (Exception ex) {
                MessageBox.Show(ex.ToString(), ApplicationHelper.GetApplicationTitle(), MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            Log("Done.");

            Invoke(() => EnableMainControls(true));
        }