private MvdCameraFrame[] CreateFrames([NotNull] CharacterImasMotionAsset mainCamera, [NotNull] ScenarioObject baseScenario, [CanBeNull] CharacterImasMotionAsset cameraAppeal, AppealType appealType) { var mainAnimation = CameraAnimation.CreateFrom(mainCamera); var appealAnimation = cameraAppeal != null?CameraAnimation.CreateFrom(cameraAppeal) : null; var animationFrameCount = mainAnimation.CameraFrames.Length; var cameraFrameList = new List <MvdCameraFrame>(); var appealTimes = appealType != AppealType.None ? AppealHelper.CollectAppealTimeInfo(baseScenario) : default; var transform60FpsTo30Fps = _conversionConfig.Transform60FpsTo30Fps; var scaleToVmdSize = _conversionConfig.ScaleToVmdSize; var unityToVmdScale = _scalingConfig.ScaleUnityToVmd; for (var mltdFrameIndex = 0; mltdFrameIndex < animationFrameCount; ++mltdFrameIndex) { if (transform60FpsTo30Fps) { if (mltdFrameIndex % 2 == 1) { continue; } } // When entering and leaving the appeal, there is also a camera control event (type 58) with `layer` > 0 (see CollectFormationChanges() for the meaning of `layer`). 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 = mltdFrameIndex; } var motionFrame = animation.CameraFrames[projectedFrameIndex]; int mvdFrameIndex; if (transform60FpsTo30Fps) { mvdFrameIndex = mltdFrameIndex / 2; } else { mvdFrameIndex = mltdFrameIndex; } var mvdFrame = new MvdCameraFrame(mvdFrameIndex); var position = new Vector3(motionFrame.PositionX, motionFrame.PositionY, motionFrame.PositionZ); position = position.FixUnityToMmd(); if (scaleToVmdSize) { position = position * unityToVmdScale; } mvdFrame.Position = position; var target = new Vector3(motionFrame.TargetX, motionFrame.TargetY, motionFrame.TargetZ); target = target.FixUnityToMmd(); if (scaleToVmdSize) { target = target * unityToVmdScale; } var delta = target - position; mvdFrame.Distance = delta.Length; var q = CameraOrientation.QuaternionLookAt(in position, in target, in Vector3.UnitY); var rotation = CameraOrientation.ComputeMmdOrientation(in q, motionFrame.AngleZ); mvdFrame.Rotation = rotation; // MVD has good support of dynamic FOV. So here we can animate its value. var fov = FocalLengthToFov(motionFrame.FocalLength); mvdFrame.FieldOfView = MathHelper.DegreesToRadians(fov); cameraFrameList.Add(mvdFrame); } return(cameraFrameList.ToArray()); }
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()); }