public static TransformHierarchies PreserveMltdSpecificOnly([NotNull] this TransformHierarchies transformHierarchies)
        {
            var newRootList = new List <TransformObject>();

            foreach (var rootObject in transformHierarchies.Roots)
            {
                var children = rootObject.Children;
                Debug.Assert(children.Length > 0, nameof(children) + "." + nameof(children.Length) + " > 0");

                foreach (var level1Child in children)
                {
                    if (!MltdRootObjects.Contains(level1Child.Name))
                    {
                        continue;
                    }

                    newRootList.Add(level1Child);
                }
            }

            var newRootArray = new TransformObject[newRootList.Count];

            for (var i = 0; i < newRootArray.Length; i += 1)
            {
                newRootArray[i] = CloneAsNewRoot(newRootList[i]);
            }

            return(TransformHierarchies.FromObjects(newRootArray));
        }
        public static TransformObject[] GetOrderedObjectArray([NotNull] this TransformHierarchies transformHierarchies)
        {
            var result = new List <TransformObject>();
            var stack  = new Stack <TransformObject>();

            foreach (var rootObject in transformHierarchies.Roots)
            {
                stack.Push(rootObject);
            }

            // DFS. We need to maintain the relative order of the hierarchy, i.e. parent is always before children.
            while (stack.Count > 0)
            {
                var obj      = stack.Pop();
                var children = obj.Children;

                if (children.Length > 0)
                {
                    for (var j = children.Length - 1; j >= 0; j -= 1)
                    {
                        stack.Push(children[j]);
                    }
                }

                result.Add(obj);
            }

            return(result.ToArray());
        }
示例#3
0
        public static TransformHierarchies LoadTransformHierarchies([NotNull] string filePath)
        {
            var manager = new AssetsManager();

            manager.LoadFiles(filePath);

            var hierarchies = TransformHierarchies.FromAssets(manager);

            return(hierarchies);
        }
示例#4
0
        private BoneNode[] BuildBoneHierarchy([NotNull] TransformHierarchies transformHierarchies, bool fixKubi)
        {
            var boneList = new List <BoneNode>();

            {
                var bonePathList = new List <string>();
                var orderedTransformHierarchy = transformHierarchies.GetOrderedObjectArray();

                var scaleUnityToPmx = _scalingConfig.ScaleUnityToPmx;

                for (var i = 0; i < orderedTransformHierarchy.Length; i += 1)
                {
                    var n = orderedTransformHierarchy[i];

                    var bonePath = n.GetFullName();
                    bonePathList.Add(bonePath);

                    var parentPath = bonePath.BreakUntilLast('/');
                    int parentIndex;

                    if (parentPath == bonePath)
                    {
                        // It's a root bone
                        parentIndex = -1;
                    }
                    else
                    {
                        parentIndex = n.Parent != null?bonePathList.IndexOf(parentPath) : -1;
                    }

                    var parent = parentIndex >= 0 ? boneList[parentIndex] : null;

                    var initialPose = n.Transform;

                    var t = initialPose.LocalPosition.ToOpenTK() * scaleUnityToPmx;
                    var q = initialPose.LocalRotation.ToOpenTK();

                    var bone = new BoneNode(parent, i, bonePath, t, q);

                    boneList.Add(bone);
                }
            }

            PostprocessBoneList(boneList, fixKubi, true);

            return(boneList.ToArray());
        }
示例#5
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);
        }
示例#6
0
 public BoneNode[] BuildBoneHierarchy([NotNull] TransformHierarchies transformHierarchies)
 {
     return(BuildBoneHierarchy(transformHierarchies, true));
 }