public VmdMotion CreateCharacterAnimation([CanBeNull] IBodyAnimationSource bodyAnimationSource, [CanBeNull] PrettyAvatar avatar, [CanBeNull] PmxModel mltdPmxModel)
        {
            IReadOnlyList <VmdBoneFrame> frames;

            if (ProcessBoneFrames && (bodyAnimationSource != null && avatar != null && mltdPmxModel != null))
            {
                frames = CreateBoneFrames(bodyAnimationSource, avatar, mltdPmxModel);
            }
            else
            {
                frames = Array.Empty <VmdBoneFrame>();
            }

            return(new VmdMotion(ModelName, frames, null, null, null, null));
        }
예제 #2
0
        public VmdMotion CreateDanceMotion([CanBeNull] IBodyAnimationSource mainDance, [NotNull] ScenarioObject baseScenario, [CanBeNull] ScenarioObject formationInfo, [CanBeNull] PrettyAvatar avatar, [CanBeNull] PmxModel mltdPmxModel, [CanBeNull] IBodyAnimationSource danceAppeal, int formationNumber, AppealType appealType)
        {
            VmdBoneFrame[] frames;

            if (ProcessBoneFrames && (mainDance != null && avatar != null && mltdPmxModel != null))
            {
                frames = CreateBoneFrames(mainDance, avatar, mltdPmxModel, baseScenario, formationInfo, danceAppeal, formationNumber, appealType);
            }
            else
            {
                frames = Array.Empty <VmdBoneFrame>();
            }

            return(new VmdMotion(ModelName, frames, null, null, null, null));
        }
예제 #3
0
        public static LoadedDance LoadDance([NotNull] string filePath, int songPosition)
        {
            IBodyAnimationSource danSource = null, apaSource = null, apgSource = null;
            var danceAnimationLoaded = false;
            var suggestedPosition    = InvalidDancePosition;

            // About number of main dance animations and special/another appeal animations:
            // Most songs have 1 dance animation (i.e. animation for 1 idol, multiplied by 3/4/5 etc.) and 1 appeal animation
            // (i.e. for 1 idol when player completes FC before the big note). Or, n dance animation and n appeal animations
            // (e.g. 虹色letters [nijile], n=2 for position 1 and 2). Some songs have 1 dance animation and n appeal animations
            // (e.g. クルリウタ [kururi], n=3 for position 1, 2 and 3). Rarely a song has n dance animations and 1 appeal animation
            // (i.e. RE@DY!! [ready0], n=5). For each dance animation, there is a dan_ object; for each appeal animation, there
            // is an apa_ or apg_ object. So there isn't really a guarantee when dan, apa or apg is non-null.

            if (!danceAnimationLoaded)
            {
                // First try with legacy bundles
                var(dan, apa, apg) = LoadDanceLegacy(filePath, songPosition, out suggestedPosition);

                if (dan != null)
                {
                    danSource            = new LegacyBodyAnimationSource(dan);
                    danceAnimationLoaded = true;
                }

                if (apa != null)
                {
                    apaSource = new LegacyBodyAnimationSource(apa);
                }

                if (apg != null)
                {
                    apgSource = new LegacyBodyAnimationSource(apg);
                }
            }

            if (!danceAnimationLoaded)
            {
                // If failed, try the new one (from ~mid 2018?)
                var(dan, apa, apg) = LoadDanceCompiled(filePath, songPosition, out suggestedPosition);

                if (dan != null)
                {
                    danSource            = new CompiledBodyAnimationSource(dan);
                    danceAnimationLoaded = true;
                }

                if (apa != null)
                {
                    apaSource = new CompiledBodyAnimationSource(apa);
                }

                if (apg != null)
                {
                    apgSource = new CompiledBodyAnimationSource(apg);
                }
            }

            var animationSet = AnimationSet.Create(danSource, apaSource, apgSource);

            return(new LoadedDance(animationSet, suggestedPosition));
        }
예제 #4
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);
        }
예제 #5
0
        public static DanceAnimationSet <IBodyAnimationSource> LoadDance([NotNull] string filePath, int motionNumber, int formationNumber)
        {
            IBodyAnimationSource defaultSource = null, anotherSource = null, specialSource = null, gorgeousSource = null;
            bool anyAnimationLoaded;
            int  suggestedPosition;

            // About number of main dance animations and special/another appeal animations:
            // Most songs have 1 dance animation (i.e. animation for 1 idol, multiplied by 3/4/5 etc.) and 1 appeal animation
            // (i.e. for 1 idol when player completes FC before the big note). Or, n dance animation and n appeal animations
            // (e.g. 虹色letters [nijile], n=2 for position 1 and 2). Some songs have 1 dance animation and n appeal animations
            // (e.g. クルリウタ [kururi], n=3 for position 1, 2 and 3). Rarely a song has n dance animations and 1 appeal animation
            // (i.e. RE@DY!! [ready0], n=5). For each dance animation, there is a dan_ object; for each appeal animation, there
            // is an apa_ or apg_ object. So there isn't really a guarantee when dan, apa or apg is non-null.

            {
                // First try with legacy bundles
                var loaded = LoadDanceLegacy(filePath, motionNumber, formationNumber);

                suggestedPosition  = loaded.SuggestedPosition;
                anyAnimationLoaded = loaded.Default != null || loaded.Special != null || loaded.Another != null || loaded.Gorgeous != null;

                if (loaded.Default != null)
                {
                    defaultSource = new LegacyBodyAnimationSource(loaded.Default);
                }

                if (loaded.Special != null)
                {
                    specialSource = new LegacyBodyAnimationSource(loaded.Special);
                }

                if (loaded.Another != null)
                {
                    anotherSource = new LegacyBodyAnimationSource(loaded.Another);
                }

                if (loaded.Gorgeous != null)
                {
                    gorgeousSource = new LegacyBodyAnimationSource(loaded.Gorgeous);
                }
            }

            if (!anyAnimationLoaded)
            {
                // If failed, try the new one (from ~mid 2018?)
                var loaded = LoadDanceCompiled(filePath, motionNumber, formationNumber);

                suggestedPosition = loaded.SuggestedPosition;

                if (loaded.Default != null)
                {
                    defaultSource = new CompiledBodyAnimationSource(loaded.Default);
                }

                if (loaded.Special != null)
                {
                    specialSource = new CompiledBodyAnimationSource(loaded.Special);
                }

                if (loaded.Another != null)
                {
                    anotherSource = new CompiledBodyAnimationSource(loaded.Another);
                }

                if (loaded.Gorgeous != null)
                {
                    gorgeousSource = new CompiledBodyAnimationSource(loaded.Gorgeous);
                }
            }

            var animationSet = AnimationSet.CreateDance(suggestedPosition, defaultSource, specialSource, anotherSource, gorgeousSource);

            return(animationSet);
        }
예제 #6
0
        private IReadOnlyList <VmdBoneFrame> CreateBoneFrames([NotNull] IBodyAnimationSource bodyMotionSource, [NotNull] PrettyAvatar avatar, [NotNull] PmxModel pmx)
        {
            var boneLookup = new BoneLookup(_conversionConfig);

            var mltdHierarchy = boneLookup.BuildBoneHierarchy(avatar);
            var pmxHierarchy  = boneLookup.BuildBoneHierarchy(pmx);

            if (_conversionConfig.AppendIKBones || _conversionConfig.AppendEyeBones)
            {
                throw new NotSupportedException("Character motion frames generation (from MLTD) is not supported when appending bones (eyes and/or IK) is enabled.");
            }
            else
            {
                Debug.Assert(mltdHierarchy.Count == pmxHierarchy.Count, "Hierarchy number should be equal between MLTD and MMD.");
            }

            foreach (var mltdBone in mltdHierarchy)
            {
                mltdBone.Initialize();
            }

            foreach (var pmxBone in pmxHierarchy)
            {
                pmxBone.Initialize();
            }

            var animation         = bodyMotionSource.Convert();
            var boneCount         = mltdHierarchy.Count;
            var animatedBoneCount = animation.BoneCount;
            var keyFrameCount     = animation.KeyFrames.Count;

            {
                void MarkNamedBone(string name)
                {
                    var bone = pmx.Bones.FirstOrDefault(b => b.Name == name);

                    if (bone != null)
                    {
                        bone.IsMltdKeyBone = true;
                    }
                    else
                    {
                        Debug.Print("Warning: trying to mark bone {0} as MLTD key bone but the bone is missing from the model.", name);
                    }
                }

                var names1 = animation.KeyFrames.Take(animatedBoneCount)
                             .Select(kf => kf.Path).ToArray();
                var names = names1.Select(boneLookup.GetVmdBoneNameFromBonePath).ToArray();
                // Mark MLTD key bones.
                foreach (var name in names)
                {
                    MarkNamedBone(name);
                }

                // Special cases
                MarkNamedBone("KUBI");
                MarkNamedBone("頭");
            }

            Debug.Assert(keyFrameCount % animatedBoneCount == 0, "keyFrameCount % animatedBoneCount == 0");

            var iterationTimes = keyFrameCount / animatedBoneCount;
            var boneFrameList  = new List <VmdBoneFrame>();

            // Reduce memory pressure of allocating new delegates (see mltdHierarchy.FirstOrDefault(...))
            var boneMatchPredicateCache = new Func <PmxBone, bool> [boneCount];

            for (var j = 0; j < boneCount; j += 1)
            {
                var refBone = pmx.Bones[j];
                boneMatchPredicateCache[j] = bone => bone.Name == refBone.Name;
            }

            // Cache `mltdBoneName`s so we don't have to compute them all in every iteration
            var boneNameCache = new Dictionary <string, string>();

            // OK, now perform iterations
            for (var i = 0; i < iterationTimes; ++i)
            {
                if (_conversionConfig.Transform60FpsTo30Fps)
                {
                    if (i % 2 == 1)
                    {
                        continue;
                    }
                }

                var keyFrameIndexStart = i * animatedBoneCount;

                for (var j = 0; j < animatedBoneCount; ++j)
                {
                    var    keyFrame = animation.KeyFrames[keyFrameIndexStart + j];
                    string mltdBoneName;

                    if (boneNameCache.ContainsKey(keyFrame.Path))
                    {
                        mltdBoneName = boneNameCache[keyFrame.Path];
                    }
                    else
                    {
                        if (keyFrame.Path.Contains("BODY_SCALE/"))
                        {
                            mltdBoneName = keyFrame.Path.Replace("BODY_SCALE/", string.Empty);
                        }
                        else
                        {
                            mltdBoneName = keyFrame.Path;
                        }

                        boneNameCache.Add(keyFrame.Path, mltdBoneName);
                    }

                    var targetBone = mltdHierarchy.SingleOrDefault(bone => bone.Name == mltdBoneName);

                    if (targetBone == null)
                    {
                        //throw new ArgumentException("Bone not found.");
                        continue; // Shika doesn't have the "POSITION" bone.
                    }

                    BoneNode transferredBone = null;

                    foreach (var kv in BoneAttachmentMap)
                    {
                        if (kv.Key == mltdBoneName)
                        {
                            transferredBone = mltdHierarchy.SingleOrDefault(bone => bone.Name == kv.Value);

                            if (transferredBone == null)
                            {
                                throw new ArgumentException();
                            }

                            break;
                        }
                    }

                    if (keyFrame.HasPositions)
                    {
                        // ReSharper disable once PossibleInvalidOperationException
                        var x = keyFrame.PositionX.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var y = keyFrame.PositionY.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var z = keyFrame.PositionZ.Value;

                        var t = new Vector3(x, y, z);

                        t = t.FixUnityToOpenTK();

                        if (_conversionConfig.ScaleToVmdSize)
                        {
                            t = t * _scalingConfig.ScaleUnityToPmx;
                        }

                        targetBone.LocalPosition = t;

                        //if (transferredBone != null) {
                        //    transferredBone.LocalPosition = t;
                        //}
                    }

                    if (keyFrame.HasRotations)
                    {
                        // ReSharper disable once PossibleInvalidOperationException
                        var x = keyFrame.AngleX.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var y = keyFrame.AngleY.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var z = keyFrame.AngleZ.Value;

                        var q = UnityRotation.EulerDeg(x, y, z);

                        q = q.FixUnityToOpenTK();

                        targetBone.LocalRotation = q;

                        if (transferredBone != null)
                        {
                            transferredBone.LocalRotation = q;
                        }
                    }
                }

                foreach (var mltdBone in mltdHierarchy)
                {
                    mltdBone.UpdateTransform();
                }

                for (var j = 0; j < boneCount; ++j)
                {
                    var pmxBone  = pmxHierarchy[j];
                    var mltdBone = mltdHierarchy[j];

                    {
                        var predicate = boneMatchPredicateCache[j];
                        var pb        = pmx.Bones.FirstOrDefault(predicate);

#if DEBUG
                        if (pb == null)
                        {
                            // Lazy evaluation of the assertion message
                            Debug.Assert(pb != null, $"PMX bone with the name \"{pmxBone.Name}\" should exist.");
                        }
#endif

                        if (!pb.IsMltdKeyBone)
                        {
                            continue;
                        }
                    }

                    var skinMatrix      = mltdBone.SkinMatrix;
                    var mPmxBindingPose = pmxBone.BindingPose;
                    var mWorld          = pmxBone.Parent?.WorldMatrix ?? Matrix4.Identity;

                    // skinMatrix == inv(mPmxBindingPose) x mLocal x mWorld
                    var mLocal = mPmxBindingPose * skinMatrix * mWorld.Inverted();

                    // Here, translation is in... world coords? WTF?
                    var t = mLocal.ExtractTranslation();
                    var q = mLocal.ExtractRotation();

                    if (pmxBone.Parent != null)
                    {
                        t = t - (pmxBone.InitialPosition - pmxBone.Parent.InitialPosition);
                    }

                    int frameIndex;

                    if (_conversionConfig.Transform60FpsTo30Fps)
                    {
                        frameIndex = i / 2;
                    }
                    else
                    {
                        frameIndex = i;
                    }

                    var vmdBoneName = boneLookup.GetVmdBoneNameFromBoneName(mltdBone.Path);
                    var boneFrame   = new VmdBoneFrame(frameIndex, vmdBoneName);

                    boneFrame.Position = t;
                    boneFrame.Rotation = q;

                    boneFrameList.Add(boneFrame);

                    pmxBone.LocalPosition = t;
                    pmxBone.LocalRotation = q;
                    pmxBone.UpdateTransform();
                }
            }

            return(boneFrameList);
        }
예제 #7
0
        private VmdBoneFrame[] CreateBoneFrames([NotNull] IBodyAnimationSource mainDance, [NotNull] PrettyAvatar avatar, [NotNull] PmxModel pmx, [NotNull] ScenarioObject baseScenario, [CanBeNull] ScenarioObject formationInfo, [CanBeNull] IBodyAnimationSource danceAppeal, int formationNumber, AppealType appealType)
        {
            var boneLookup = new BoneLookup(_conversionConfig);

            var mltdHierarchy = boneLookup.BuildBoneHierarchy(avatar);

            mltdHierarchy.AssertAllUnique();

            var pmxHierarchy = boneLookup.BuildBoneHierarchy(pmx);

            pmxHierarchy.AssertAllUnique();

            if (_conversionConfig.AppendIKBones || _conversionConfig.AppendEyeBones)
            {
                throw new NotSupportedException("Character motion frames generation (from MLTD) is not supported when appending bones (eyes and/or IK) is enabled.");
            }
            else
            {
                Debug.Assert(mltdHierarchy.Length == pmxHierarchy.Length, "Hierarchy number should be equal between MLTD and MMD.");
            }

            foreach (var mltdBone in mltdHierarchy)
            {
                mltdBone.Initialize();
            }

            foreach (var pmxBone in pmxHierarchy)
            {
                pmxBone.Initialize();
            }

            var mainAnimation     = mainDance.Convert();
            var appealAnimation   = danceAppeal?.Convert();
            var mltdBoneCount     = mltdHierarchy.Length;
            var animatedBoneCount = mainAnimation.BoneCount;
            var keyFrameCount     = mainAnimation.KeyFrames.Length;

            {
                var names1 = mainAnimation.KeyFrames.Take(animatedBoneCount)
                             .Select(kf => kf.Path).ToArray();
                var names = names1.Select(boneLookup.GetVmdBoneNameFromBonePath).ToArray();

                // Mark MLTD key bones.
                foreach (var name in names)
                {
                    MarkNamedBoneAsKeyBone(pmx, name);
                }

                // Special cases
                MarkNamedBoneAsKeyBone(pmx, "KUBI");
                MarkNamedBoneAsKeyBone(pmx, "頭");
            }

            Debug.Assert(keyFrameCount % animatedBoneCount == 0, "keyFrameCount % animatedBoneCount == 0");

            // Use this value to export visible frames only
            var resultFrameCount = (int)(mainAnimation.Duration * FrameRate.Mltd);
            // Use this value to export all frames, including invisible frames in normal MVs (e.g. seek targets)
            // var resultFrameCount = keyFrameCount / animatedBoneCount;

            var boneFrameList = new List <VmdBoneFrame>();

            // Reduce memory pressure of allocating new delegates (see mltdHierarchy.FirstOrDefault(...))
            var boneMatchPredicateCache = new Predicate <PmxBone> [mltdBoneCount];

            for (var j = 0; j < mltdBoneCount; j += 1)
            {
                var refBone = pmx.Bones[j];
                boneMatchPredicateCache[j] = bone => bone.Name == refBone.Name;
            }

            // Cache `mltdBoneName`s so we don't have to compute them all in every iteration
            var boneNameCache = new Dictionary <string, string>();

            var transform60FpsTo30Fps = _conversionConfig.Transform60FpsTo30Fps;
            var scaleToVmdSize        = _conversionConfig.ScaleToVmdSize;
            var unityToVmdScale       = _scalingConfig.ScaleUnityToVmd;

            var baseFormationList   = CollectFormationChanges(formationInfo, AppealType.None);
            var appealFormationList = CollectFormationChanges(formationInfo, appealType);
            var appealTimes         = AppealHelper.CollectAppealTimeInfo(baseScenario);
            var seekFrameControls   = CollectSeekFrames(baseScenario, formationNumber);

            var seekFrameCounter = 0;
            var lastSoughtFrame  = -1;

            // OK, now perform iterations
            for (var mltdFrameIndex = 0; mltdFrameIndex < resultFrameCount; ++mltdFrameIndex)
            {
                if (transform60FpsTo30Fps)
                {
                    if (mltdFrameIndex % 2 == 1)
                    {
                        continue;
                    }
                }

                var shouldUseAppeal = appealType != AppealType.None && (appealTimes.StartFrame <= mltdFrameIndex && mltdFrameIndex < appealTimes.EndFrame) && appealAnimation != null;

                var animation = shouldUseAppeal ? appealAnimation : mainAnimation;

                int projectedFrameIndex;

                if (shouldUseAppeal)
                {
                    var indexInAppeal = mltdFrameIndex - appealTimes.StartFrame;

                    if (indexInAppeal >= appealAnimation.FrameCount)
                    {
                        indexInAppeal = appealAnimation.FrameCount - 1;
                    }

                    // `indexInAppeal`, unlike `mltdFrameIndex`, has not been scaled yet
                    if (transform60FpsTo30Fps)
                    {
                        projectedFrameIndex = indexInAppeal / 2;
                    }
                    else
                    {
                        projectedFrameIndex = indexInAppeal;
                    }
                }
                else
                {
                    projectedFrameIndex = CalculateSeekFrameTarget(mltdFrameIndex, seekFrameControls, ref lastSoughtFrame, ref seekFrameCounter);
                }

                var formationList = shouldUseAppeal ? appealFormationList : baseFormationList;

                formationList.TryGetCurrentValue(mltdFrameIndex, out var formations);

                Vector4 idolOffset;

                if (formations == null || formations.Length < formationNumber)
                {
                    idolOffset = Vector4.Zero;
                }
                else
                {
                    idolOffset = formations[formationNumber - 1];
                }

                var keyFrameIndexStart = projectedFrameIndex * animatedBoneCount;

                for (var j = 0; j < animatedBoneCount; ++j)
                {
                    var keyFrame     = animation.KeyFrames[keyFrameIndexStart + j];
                    var mltdBoneName = GetMltdBoneNameWithoutBodyScale(boneNameCache, keyFrame);

                    // Uniqueness is asserted above
                    var targetBone = mltdHierarchy.Find(bone => bone.Name == mltdBoneName);

                    if (targetBone == null)
                    {
                        //throw new ArgumentException("Bone not found.");
                        continue; // Shika doesn't have the "POSITION" bone.
                    }

                    BoneNode transferredBone = null;

                    foreach (var kv in BoneAttachmentMap)
                    {
                        if (kv.Key != mltdBoneName)
                        {
                            continue;
                        }

                        var attachmentTarget = kv.Value;

                        // Uniqueness is asserted above
                        transferredBone = mltdHierarchy.Find(bone => bone.Name == attachmentTarget);

                        if (transferredBone == null)
                        {
                            throw new ArgumentException("Cannot find transferred bone.");
                        }

                        break;
                    }

                    if (keyFrame.HasPositions)
                    {
                        // ReSharper disable once PossibleInvalidOperationException
                        var x = keyFrame.PositionX.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var y = keyFrame.PositionY.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var z = keyFrame.PositionZ.Value;

                        if (string.Equals(keyFrame.Path, "MODEL_00", StringComparison.Ordinal))
                        {
                            var worldRotation = Quaternion.FromAxisAngle(Vector3.UnitY, MathHelper.DegreesToRadians(idolOffset.W));
                            var newOrigin     = worldRotation * new Vector3(x, y, z);
                            var newPosition   = newOrigin + idolOffset.Xyz;

                            (x, y, z) = (newPosition.X, newPosition.Y, newPosition.Z);
                        }

                        var t = new Vector3(x, y, z);

                        t = t.FixUnityToMmd();

                        if (scaleToVmdSize)
                        {
                            t = t * unityToVmdScale;
                        }

                        targetBone.LocalPosition = t;

                        //if (transferredBone != null) {
                        //    transferredBone.LocalPosition = t;
                        //}
                    }

                    if (keyFrame.HasRotations)
                    {
                        // ReSharper disable once PossibleInvalidOperationException
                        var x = keyFrame.AngleX.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var y = keyFrame.AngleY.Value;
                        // ReSharper disable once PossibleInvalidOperationException
                        var z = keyFrame.AngleZ.Value;

                        if (string.Equals(keyFrame.Path, "MODEL_00", StringComparison.Ordinal))
                        {
                            // The W component stores rotation
                            y += idolOffset.W;
                        }

                        var q = UnityRotation.EulerDeg(x, y, z);

                        q = q.FixUnityToOpenTK();

                        targetBone.LocalRotation = q;

                        if (transferredBone != null)
                        {
                            transferredBone.LocalRotation = q;
                        }
                    }
                }

                foreach (var mltdBone in mltdHierarchy)
                {
                    mltdBone.UpdateTransform();
                }

                for (var j = 0; j < mltdBoneCount; ++j)
                {
                    var pmxBone  = pmxHierarchy[j];
                    var mltdBone = mltdHierarchy[j];

                    {
                        var predicate = boneMatchPredicateCache[j];
                        var pb        = pmx.Bones.Find(predicate);

#if DEBUG
                        if (pb == null)
                        {
                            // Lazy evaluation of the assertion message
                            Debug.Assert(pb != null, $"PMX bone with the name \"{pmxBone.Name}\" should exist.");
                        }
#endif

                        if (!pb.IsMltdKeyBone)
                        {
                            continue;
                        }
                    }

                    var skinMatrix      = mltdBone.SkinMatrix;
                    var mPmxBindingPose = pmxBone.BindingPose;
                    var mWorld          = pmxBone.Parent?.WorldMatrix ?? Matrix4.Identity;

                    // skinMatrix == inv(mPmxBindingPose) x mLocal x mWorld
                    var mLocal = mPmxBindingPose * skinMatrix * mWorld.Inverted();

                    // Here, translation is in... world coords? WTF?
                    var t = mLocal.ExtractTranslation();
                    var q = mLocal.ExtractRotation();

                    if (pmxBone.Parent != null)
                    {
                        t = t - (pmxBone.InitialPosition - pmxBone.Parent.InitialPosition);
                    }

                    int vmdFrameIndex;

                    if (_conversionConfig.Transform60FpsTo30Fps)
                    {
                        vmdFrameIndex = mltdFrameIndex / 2;
                    }
                    else
                    {
                        vmdFrameIndex = mltdFrameIndex;
                    }

                    var mltdBoneName = GetMltdBoneNameWithoutBodyScale(boneNameCache, mltdBone.Path);
                    var vmdBoneName  = boneLookup.GetVmdBoneNameFromBoneName(mltdBone.Path);
                    var boneFrame    = new VmdBoneFrame(vmdFrameIndex, vmdBoneName);

                    var isMovable = BoneLookup.IsBoneMovable(mltdBoneName);

                    boneFrame.Position = isMovable ? t : Vector3.Zero;
                    boneFrame.Rotation = q;

                    boneFrameList.Add(boneFrame);

                    pmxBone.LocalPosition = t;
                    pmxBone.LocalRotation = q;
                    pmxBone.UpdateTransform();
                }
            }

            return(boneFrameList.ToArray());
        }