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); }
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)); }
// Some heavy stuff private void Initialize1() { ResHelper.LoadHeadMesh(); _bodyMesh = ResHelper.LoadBodyMesh(); _bodyAvatar = ResHelper.LoadBodyAvatar(); _headMesh = ResHelper.LoadHeadMesh(); _headAvatar = ResHelper.LoadHeadAvatar(); Debug.Assert(_bodyAvatar != null, nameof(_bodyAvatar) + " != null"); Debug.Assert(_bodyMesh != null, nameof(_bodyMesh) + " != null"); Debug.Assert(_headAvatar != null, nameof(_headAvatar) + " != null"); Debug.Assert(_headMesh != null, nameof(_headMesh) + " != null"); _bodyBoneList = ResHelper.BuildBoneHierachy(_bodyAvatar, _bodyMesh); _headBoneList = ResHelper.BuildBoneHierachy(_headAvatar, _headMesh); do { // Fix "KUBI" (neck) bone's parent var kubiParent = _bodyBoneList.SingleOrDefault(bn => bn.Path == "MODEL_00/BASE/MUNE1/MUNE2/KUBI"); var kubiBone = _headBoneList.SingleOrDefault(bn => bn.Path == "KUBI"); Debug.Assert(kubiParent != null); Debug.Assert(kubiBone != null); kubiParent.AddChild(kubiBone); Debug.Assert(kubiBone.Parent != null); // Don't forget to remove it from its old parent (or it will be updated twice from two parents). // The original parents and grandparents of KUBI are not needed; they are just like model anchors and shouldn't be animated. // See decompiled model for more information. kubiBone.Parent.RemoveChild(kubiBone); kubiBone.Parent = kubiParent; // Set its new initial parameters. // Since the two bones (KUBI and MODEL_00/BASE/MUNE1/MUNE2/KUBI) actually share the exact same transforms, // set its local transform to identity (t=0, q=0). kubiBone.InitialPosition = Vector3.Zero; kubiBone.InitialRotation = Quaternion.Identity; kubiBone.LocalPosition = Vector3.Zero; kubiBone.LocalRotation = Quaternion.Identity; // Now inform the rest of the head bones the new bone hierarchy. Force them to do so. foreach (var bone in _headBoneList) { bone.Initialize(true); } } while (false); // TODO: FIXME: new format is not supported here (it's hard-coded) but it is handled in MillionDance. (_danceData, _, _) = ResHelper.LoadDance(); #if DEBUG do { var influencingBones = _bodyBoneList.Where((_, i) => _bodyMesh.Skin.Any(sk => sk.Any(a => a.BoneIndex == i))); Debug.Print("Bones that influences the mesh:"); foreach (var bone in influencingBones) { Debug.Print("#{0} \"{1}\"", bone.Index, bone.Path); } } while (false); #endif _animation = new LegacyBodyAnimationSource(_danceData).Convert(); #if DEBUG do { var animatedBoneNames = _animation.KeyFrames.Select(f => f.Path).Distinct(); Debug.Print("Animated bone names:"); foreach (var boneName in animatedBoneNames) { Debug.Print(boneName); } } while (false); #endif _initialized1 = true; }