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 IReadOnlyList <MvdCameraMotion> CreateCameraMotions([NotNull] CharacterImasMotionAsset cameraMotion) { const string cameraName = "カメラ00"; // Localization error (Mr. mogg, please!), MUST NOT USE "Camera00"! MvdCameraObject CreateCamera() { var cam = new MvdCameraObject(); cam.DisplayName = cameraName; cam.EnglishName = cameraName; cam.Id = 0; return(cam); } IReadOnlyList <MvdCameraFrame> CreateFrames() { var animation = CameraAnimation.CreateFrom(cameraMotion); var animationFrameCount = animation.CameraFrames.Count; var cameraFrameList = new List <MvdCameraFrame>(); for (var i = 0; i < animationFrameCount; ++i) { if (_conversionConfig.Transform60FpsTo30Fps) { if (i % 2 == 1) { continue; } } int frameIndex; if (_conversionConfig.Transform60FpsTo30Fps) { frameIndex = i / 2; } else { frameIndex = i; } var frame = animation.CameraFrames[i]; var mvdFrame = new MvdCameraFrame(frameIndex); var pos = new Vector3(frame.PositionX, frame.PositionY, frame.PositionZ); pos = pos.FixUnityToOpenTK(); if (_conversionConfig.ScaleToVmdSize) { pos = pos * _scalingConfig.ScaleUnityToVmd; } mvdFrame.Position = pos; var target = new Vector3(frame.TargetX, frame.TargetY, frame.TargetZ); target = target.FixUnityToOpenTK(); if (_conversionConfig.ScaleToVmdSize) { target = target * _scalingConfig.ScaleUnityToVmd; } var delta = target - pos; mvdFrame.Distance = delta.Length; var lookAtMatrix = Matrix4.LookAt(pos, target, Vector3.UnitY); var q = lookAtMatrix.ExtractRotation(); var rot = q.DecomposeRad(); rot.Z = MathHelper.DegreesToRadians(frame.AngleZ); rot.Y = MathHelper.Pi - rot.Y; if (rot.Y < 0) { rot.Y += MathHelper.TwoPi; } if (delta.Z < 0) { rot.Y = -(MathHelper.Pi + rot.Y); } rot.Z = -rot.Z; if (rot.X < -MathHelper.PiOver2) { rot.X = rot.X + MathHelper.Pi; } else if (rot.X > MathHelper.PiOver2) { rot.X = -(MathHelper.Pi - rot.X); } mvdFrame.Rotation = rot; // MVD has good support of dynamic FOV. So here we can animate its value. var fov = FocalLengthToFov(frame.FocalLength); mvdFrame.FieldOfView = MathHelper.DegreesToRadians(fov); cameraFrameList.Add(mvdFrame); } return(cameraFrameList); } IReadOnlyList <MvdCameraPropertyFrame> CreatePropertyFrames() { var list = new List <MvdCameraPropertyFrame>(); var f = new MvdCameraPropertyFrame(0); f.Enabled = true; f.IsPerspective = true; f.Alpha = 1; f.EffectEnabled = true; f.DynamicFovEnabled = false; f.DynamicFovRate = 0.1f; f.DynamicFovCoefficient = 1; f.RelatedBoneId = -1; f.RelatedModelId = -1; list.Add(f); return(list); } // https://photo.stackexchange.com/questions/41273/how-to-calculate-the-fov-in-degrees-from-focal-length-or-distance float FocalLengthToFov(float focalLength) { // By experiments, MLTD seems to use a 15mm or 16mm camera. const float sensorSize = 12; // unit: mm, as the unit of MLTD camera frame is also mm var fovRad = 2 * (float)Math.Atan((sensorSize / 2) / focalLength); var fovDeg = MathHelper.RadiansToDegrees(fovRad); return(fovDeg); } var camera = CreateCamera(); var cameraFrames = CreateFrames(); var cameraPropertyFrames = CreatePropertyFrames(); var result = new MvdCameraMotion(camera, cameraFrames, cameraPropertyFrames); result.DisplayName = cameraName; result.EnglishName = cameraName; return(new[] { result }); }