diff --git a/TombLib/TombLib.Forms/Controls/BezierCurveEditor.cs b/TombLib/TombLib.Forms/Controls/BezierCurveEditor.cs index 66be7c6f3b..16e2d35d7f 100644 --- a/TombLib/TombLib.Forms/Controls/BezierCurveEditor.cs +++ b/TombLib/TombLib.Forms/Controls/BezierCurveEditor.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using System.Drawing; +using System.Drawing.Drawing2D; using System.Numerics; using System.Windows.Forms; using TombLib.Types; @@ -71,18 +72,13 @@ public void UpdateUI() private void AdjustHandlesForLinearCurve() { - if (_bezierCurve.StartHandle == _bezierCurve.Start) - { - - _controlPoints[0] = new Vector2(0, Height); - _controlPoints[1] = new Vector2(Width / 3.0f, Height * 2.0f / 3.0f); - } + if (_bezierCurve.StartHandle != _bezierCurve.Start || _bezierCurve.EndHandle != _bezierCurve.End) + return; - if (_bezierCurve.EndHandle == _bezierCurve.End) - { - _controlPoints[2] = new Vector2(2 * Width / 3.0f, Height / 3.0f); - _controlPoints[3] = new Vector2(Width, 0); - } + _controlPoints[0] = new Vector2(0, Height); + _controlPoints[1] = new Vector2(Width / 3.0f, Height * 2.0f / 3.0f); + _controlPoints[2] = new Vector2(2 * Width / 3.0f, Height / 3.0f); + _controlPoints[3] = new Vector2(Width, 0); } private Vector2 TransformToBezier(Vector2 point) @@ -235,6 +231,35 @@ protected override void OnMouseDoubleClick(MouseEventArgs e) } } + public static void DrawPreview(Graphics g, Rectangle rect, BezierCurve2 curve, int padding = 2) + { + int x = rect.X + padding; + int y = rect.Y + padding; + int w = rect.Width - padding * 2; + int h = rect.Height - padding * 2; + + if (w <= 0 || h <= 0) + return; + + using (var pen = new Pen(Colors.LightText, 1.0f)) + { + pen.StartCap = LineCap.Round; + pen.EndCap = LineCap.Round; + + int steps = Math.Max(w / 2, 8); + var points = new PointF[steps + 1]; + for (int i = 0; i <= steps; i++) + { + float alpha = (float)i / steps; + var p = curve.GetPoint(alpha); + points[i] = new PointF(x + p.X * w, y + (1.0f - p.Y) * h); + } + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.DrawLines(pen, points); + } + } + protected override void OnResize(EventArgs e) { base.OnResize(e); diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index fcedc25a33..a604c753c0 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -667,7 +667,6 @@ public void Write(BinaryWriterEx writer) foreach (var animation in Animations) { writer.Write(animation.StateID); - writer.Write(animation.Interpolation); writer.Write(animation.FrameEnd); writer.Write(animation.NextAnimation); writer.Write(animation.NextFrame); @@ -701,22 +700,10 @@ public void Write(BinaryWriterEx writer) writer.Write(fixedMotionCurveZ.StartHandle); writer.Write(fixedMotionCurveZ.EndHandle); - if (animation.KeyFrames.Count == 0) - { - var defaultKeyFrame = new TombEngineKeyFrame(); - defaultKeyFrame.BoneOrientations = new List(Enumerable.Repeat(Quaternion.Identity, NumMeshes)); - - writer.Write(1); - defaultKeyFrame.Write(writer); - } - else - { - writer.Write(animation.KeyFrames.Count); - foreach (var keyFrame in animation.KeyFrames) - { - keyFrame.Write(writer); - } - } + // Write pre-baked frames. + writer.Write(animation.InterpolatedFrames.Count); + foreach (var frame in animation.InterpolatedFrames) + frame.Write(writer); writer.Write(animation.StateChanges.Count); foreach (var stateChange in animation.StateChanges) @@ -725,9 +712,9 @@ public void Write(BinaryWriterEx writer) writer.Write(stateChange.FrameLow); writer.Write(stateChange.FrameHigh); writer.Write(stateChange.NextAnimation); - writer.Write(stateChange.NextFrameLow); - writer.Write(stateChange.NextFrameHigh); - writer.Write(stateChange.BlendFrameCount); + writer.Write(stateChange.NextLowFrame); + writer.Write(stateChange.NextHighFrame); + writer.Write(stateChange.BlendFrames); writer.Write(stateChange.BlendCurve.Start); writer.Write(stateChange.BlendCurve.End); writer.Write(stateChange.BlendCurve.StartHandle); @@ -747,7 +734,7 @@ public void Write(BinaryWriterEx writer) } } - writer.Write(animation.Flags); + writer.Write((int)animation.RootMotion.Flags); } } } @@ -759,9 +746,9 @@ public struct TombEngineStateChange public int FrameLow; public int FrameHigh; public int NextAnimation; - public int NextFrameLow; - public int NextFrameHigh; - public int BlendFrameCount; + public int NextLowFrame; + public int NextHighFrame; + public int BlendFrames; public BezierCurve2 BlendCurve; } @@ -778,10 +765,11 @@ public struct TombEngineAnimation public Vector3 VelocityStart; public Vector3 VelocityEnd; public List KeyFrames; + public List InterpolatedFrames; public List StateChanges; public int NumAnimCommands; public List CommandData; - public int Flags; + public WadAnimRootMotionSettings RootMotion; } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -793,17 +781,8 @@ public class TombEngineKeyFrame public void Write(BinaryWriterEx writer) { - var center = new Vector3( - BoundingBox.X1 + BoundingBox.X2, - BoundingBox.Y1 + BoundingBox.Y2, - BoundingBox.Z1 + BoundingBox.Z2) / 2; - var extents = new Vector3( - BoundingBox.X2 - BoundingBox.X1, - BoundingBox.Y2 - BoundingBox.Y1, - BoundingBox.Z2 - BoundingBox.Z1) / 2; - - writer.Write(center); - writer.Write(extents); + writer.Write(BoundingBox.Center); + writer.Write(BoundingBox.Extents); writer.Write(RootOffset); writer.Write(BoneOrientations.Count); @@ -820,6 +799,28 @@ public struct TombEngineBoundingBox public short Y2; public short Z1; public short Z2; + + public Vector3 Center + { + get + { + return new Vector3( + X1 + X2, + Y1 + Y2, + Z1 + Z2) / 2; + } + } + + public Vector3 Extents + { + get + { + return new Vector3( + X2 - X1, + Y2 - Y1, + Z2 - Z1) / 2; + } + } } [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Wad.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Wad.cs index 02f3942f33..522d9672f7 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Wad.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Wad.cs @@ -287,6 +287,7 @@ public void ConvertWad2DataToTombEngine() newAnimation.VelocityStart = new Vector3(oldAnimation.StartLateralVelocity, 0, oldAnimation.StartVelocity); newAnimation.VelocityEnd = new Vector3(oldAnimation.EndLateralVelocity, 0, oldAnimation.EndVelocity); newAnimation.KeyFrames = new List(); + newAnimation.InterpolatedFrames = new List(); newAnimation.StateChanges = new List(); newAnimation.NumAnimCommands = oldAnimation.AnimCommands.Count; newAnimation.CommandData = new List(); @@ -314,6 +315,10 @@ public void ConvertWad2DataToTombEngine() newAnimation.KeyFrames.Add(newFrame); } + // Bake interpolated frames from keyframes and pass root motion settings. + BakeInterpolatedFrames(newAnimation, oldMoveable.Meshes.Count()); + newAnimation.RootMotion = oldAnimation.RootMotion; + // Add anim commands foreach (var command in oldAnimation.AnimCommands) { @@ -325,7 +330,7 @@ public void ConvertWad2DataToTombEngine() newAnimation.CommandData.Add(new Vector3(command.Parameter1, command.Parameter2, command.Parameter3)); break; - + case WadAnimCommandType.SetJumpDistance: newAnimation.CommandData.Add(2); @@ -387,9 +392,9 @@ public void ConvertWad2DataToTombEngine() newStateChange.FrameLow = unchecked((int)(dispatch.InFrame)); newStateChange.FrameHigh = unchecked((int)(dispatch.OutFrame)); newStateChange.NextAnimation = checked((int)(dispatch.NextAnimation)); - newStateChange.NextFrameLow = (int)dispatch.NextFrameLow; - newStateChange.NextFrameHigh = (int)dispatch.NextFrameHigh; - newStateChange.BlendFrameCount = (int)dispatch.BlendFrameCount; + newStateChange.NextLowFrame = (int)dispatch.NextLowFrame; + newStateChange.NextHighFrame = (int)dispatch.NextHighFrame; + newStateChange.BlendFrames = (int)dispatch.BlendFrames; newStateChange.BlendCurve = dispatch.BlendCurve.Clone(); newAnimation.StateChanges.Add(newStateChange); @@ -540,6 +545,56 @@ public void ConvertWad2DataToTombEngine() } } + private static void BakeInterpolatedFrames(TombEngineAnimation animation, int meshCount) + { + // Write dummy frame if no keyframes exist. + if (animation.KeyFrames.Count == 0) + { + var defaultKeyFrame = new TombEngineKeyFrame(); + defaultKeyFrame.BoneOrientations = new List(Enumerable.Repeat(Quaternion.Identity, meshCount)); + animation.InterpolatedFrames.Add(defaultKeyFrame); + return; + } + + float alphaStep = 1.0f / (float)Math.Max(1, animation.Interpolation); + for (int i = 0; i < animation.KeyFrames.Count; i++) + { + var currentKeyframe = animation.KeyFrames[i]; + animation.InterpolatedFrames.Add(currentKeyframe); + + if (i == (animation.KeyFrames.Count - 1)) + break; + + var nextKeyframe = animation.KeyFrames[i + 1]; + + // Add interpolated frames between keyframes. + for (int j = 1; j < animation.Interpolation; j++) + { + float alpha = alphaStep * j; + + var center = Vector3.Lerp(currentKeyframe.BoundingBox.Center, nextKeyframe.BoundingBox.Center, alpha); + var extents = Vector3.Lerp(currentKeyframe.BoundingBox.Extents, nextKeyframe.BoundingBox.Extents, alpha); + var rootPos = Vector3.Lerp(currentKeyframe.RootOffset, nextKeyframe.RootOffset, alpha); + + var boneOrients = new List(); + for (int k = 0; k < currentKeyframe.BoneOrientations.Count; k++) + boneOrients.Add(Quaternion.Slerp(currentKeyframe.BoneOrientations[k], nextKeyframe.BoneOrientations[k], alpha)); + + var frame = new TombEngineKeyFrame(); + frame.BoundingBox = new TombEngineBoundingBox(); + frame.BoundingBox.X1 = (short)(center.X - extents.X); + frame.BoundingBox.X2 = (short)(center.X + extents.X); + frame.BoundingBox.Y1 = (short)(center.Y - extents.Y); + frame.BoundingBox.Y2 = (short)(center.Y + extents.Y); + frame.BoundingBox.Z1 = (short)(center.Z - extents.Z); + frame.BoundingBox.Z2 = (short)(center.Z + extents.Z); + frame.RootOffset = rootPos; + frame.BoneOrientations = boneOrients; + animation.InterpolatedFrames.Add(frame); + } + } + } + public static short ToTrAngle(float angle) { double result = Math.Round(angle * (65536.0f / 360.0f)); diff --git a/TombLib/TombLib/LevelData/Compilers/Wad.cs b/TombLib/TombLib/LevelData/Compilers/Wad.cs index e8097a3d5f..23d7bf2c1d 100644 --- a/TombLib/TombLib/LevelData/Compilers/Wad.cs +++ b/TombLib/TombLib/LevelData/Compilers/Wad.cs @@ -461,7 +461,7 @@ public void ConvertWad2DataToTrData(Level l) newAnimDispatch.Low = unchecked((ushort)(dispatch.InFrame + newAnimation.FrameStart)); newAnimDispatch.High = unchecked((ushort)(dispatch.OutFrame + newAnimation.FrameStart)); newAnimDispatch.NextAnimation = checked((ushort)(dispatch.NextAnimation + lastAnimation)); - newAnimDispatch.NextFrame = dispatch.NextFrameLow; + newAnimDispatch.NextFrame = dispatch.NextLowFrame; _animDispatches.Add(newAnimDispatch); lastAnimDispatch++; diff --git a/TombLib/TombLib/Types/BezierCurve2.cs b/TombLib/TombLib/Types/BezierCurve2.cs index 96b8a29198..0be77d8306 100644 --- a/TombLib/TombLib/Types/BezierCurve2.cs +++ b/TombLib/TombLib/Types/BezierCurve2.cs @@ -193,7 +193,6 @@ public override int GetHashCode() first.EndHandle == second.EndHandle; } - public static bool operator !=(BezierCurve2 first, BezierCurve2 second) => !(first == second); } } diff --git a/TombLib/TombLib/Wad/Tr4Wad/Tr4WadOperations.cs b/TombLib/TombLib/Wad/Tr4Wad/Tr4WadOperations.cs index 2413347015..04aad05f22 100644 --- a/TombLib/TombLib/Wad/Tr4Wad/Tr4WadOperations.cs +++ b/TombLib/TombLib/Wad/Tr4Wad/Tr4WadOperations.cs @@ -369,7 +369,7 @@ internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad old ad.InFrame = (ushort)(wadAd.Low - newFrameStart); ad.OutFrame = (ushort)(wadAd.High - newFrameStart); ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.AnimationIndex) % numAnimations); - ad.NextFrameLow = (ushort)wadAd.NextFrame; + ad.NextLowFrame = (ushort)wadAd.NextFrame; sc.Dispatches.Add(ad); } @@ -535,7 +535,7 @@ internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad old if (animDispatch.NextAnimation > short.MaxValue) { animDispatch.NextAnimation = 0; - animDispatch.NextFrameLow = 0; + animDispatch.NextLowFrame = 0; continue; } @@ -543,9 +543,9 @@ internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad old { // HACK: In some cases dispatches have invalid NextFrame. // From tests it seems that's ok to make NextFrame equal to max frame number. - animDispatch.NextFrameLow -= frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0]; - if (animDispatch.NextFrameLow > frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]) - animDispatch.NextFrameLow = frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]; + animDispatch.NextLowFrame -= frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0]; + if (animDispatch.NextLowFrame > frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]) + animDispatch.NextLowFrame = frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]; } stateChange.Dispatches[j] = animDispatch; } diff --git a/TombLib/TombLib/Wad/TrLevels/TrLevelOperations.cs b/TombLib/TombLib/Wad/TrLevels/TrLevelOperations.cs index 91c9147b8d..3a68d11eb6 100644 --- a/TombLib/TombLib/Wad/TrLevels/TrLevelOperations.cs +++ b/TombLib/TombLib/Wad/TrLevels/TrLevelOperations.cs @@ -385,7 +385,7 @@ public static WadMoveable ConvertTrLevelMoveableToWadMoveable(Wad2 wad, TrLevel ad.InFrame = (ushort)(wadAd.Low - oldAnimation.FrameStart); ad.OutFrame = (ushort)(wadAd.High - oldAnimation.FrameStart); ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.Animation) % numAnimations); - ad.NextFrameLow = (ushort)wadAd.NextFrame; + ad.NextLowFrame = (ushort)wadAd.NextFrame; sc.Dispatches.Add(ad); } @@ -575,7 +575,7 @@ public static WadMoveable ConvertTrLevelMoveableToWadMoveable(Wad2 wad, TrLevel { WadAnimDispatch animDispatch = stateChange.Dispatches[J]; if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]] != 0) - animDispatch.NextFrameLow = (ushort)(animDispatch.NextFrameLow - frameBases[newMoveable.Animations[animDispatch.NextAnimation]]); + animDispatch.NextLowFrame = (ushort)(animDispatch.NextLowFrame - frameBases[newMoveable.Animations[animDispatch.NextAnimation]]); stateChange.Dispatches[J] = animDispatch; } } diff --git a/TombLib/TombLib/Wad/Wad2Chunks.cs b/TombLib/TombLib/Wad/Wad2Chunks.cs index 4d54eb0a1b..ec376cb27f 100644 --- a/TombLib/TombLib/Wad/Wad2Chunks.cs +++ b/TombLib/TombLib/Wad/Wad2Chunks.cs @@ -120,6 +120,7 @@ public static class Wad2Chunks /********/public static readonly ChunkId Animation3 = ChunkId.FromString("W2Ani3"); /**********/public static readonly ChunkId AnimationVelocities = ChunkId.FromString("W2AniV"); /**********/public static readonly ChunkId AnimationName = ChunkId.FromString("W2AnmName"); + /**********/public static readonly ChunkId AnimationRootMotion = ChunkId.FromString("W2AniRM"); /**********/public static readonly ChunkId StateChanges = ChunkId.FromString("W2StChs"); /************/public static readonly ChunkId StateChange = ChunkId.FromString("W2StCh"); /**************/public static readonly ChunkId Dispatches = ChunkId.FromString("W2Disps"); diff --git a/TombLib/TombLib/Wad/Wad2Loader.cs b/TombLib/TombLib/Wad/Wad2Loader.cs index 0210505d51..266bb58353 100644 --- a/TombLib/TombLib/Wad/Wad2Loader.cs +++ b/TombLib/TombLib/Wad/Wad2Loader.cs @@ -773,6 +773,13 @@ private static bool LoadMoveables(ChunkReader chunkIO, ChunkId idOuter, Wad2 wad animation.StartLateralVelocity = velocities.Z; animation.EndLateralVelocity = velocities.W; } + else if (id3 == Wad2Chunks.AnimationRootMotion) + { + animation.RootMotion = new WadAnimRootMotionSettings + { + Flags = (WadAnimRootMotionFlags)chunkIO.ReadChunkInt(chunkSize3), + }; + } else if (id3 == Wad2Chunks.KeyFrame) { var keyframe = new WadKeyFrame(); @@ -824,12 +831,12 @@ private static bool LoadMoveables(ChunkReader chunkIO, ChunkId idOuter, Wad2 wad dispatch.InFrame = LEB128.ReadUShort(chunkIO.Raw); dispatch.OutFrame = LEB128.ReadUShort(chunkIO.Raw); dispatch.NextAnimation = LEB128.ReadUShort(chunkIO.Raw); - dispatch.NextFrameLow = LEB128.ReadUShort(chunkIO.Raw); + dispatch.NextLowFrame = LEB128.ReadUShort(chunkIO.Raw); if (id4 == Wad2Chunks.Dispatch2) { - dispatch.NextFrameHigh = LEB128.ReadUShort(chunkIO.Raw); - dispatch.BlendFrameCount = LEB128.ReadUShort(chunkIO.Raw); + dispatch.NextHighFrame = LEB128.ReadUShort(chunkIO.Raw); + dispatch.BlendFrames = LEB128.ReadUShort(chunkIO.Raw); chunkIO.ReadChunks((id5, chunkSize5) => { diff --git a/TombLib/TombLib/Wad/Wad2Writer.cs b/TombLib/TombLib/Wad/Wad2Writer.cs index 4e40292c14..9822f4ce0a 100644 --- a/TombLib/TombLib/Wad/Wad2Writer.cs +++ b/TombLib/TombLib/Wad/Wad2Writer.cs @@ -406,10 +406,10 @@ private static void WriteMoveables(ChunkWriter chunkIO, Wad2 wad, List Flags.HasFlag(WadAnimRootMotionFlags.TranslationX); set => SetFlag(WadAnimRootMotionFlags.TranslationX, value); } + public bool TranslationY { get => Flags.HasFlag(WadAnimRootMotionFlags.TranslationY); set => SetFlag(WadAnimRootMotionFlags.TranslationY, value); } + public bool TranslationZ { get => Flags.HasFlag(WadAnimRootMotionFlags.TranslationZ); set => SetFlag(WadAnimRootMotionFlags.TranslationZ, value); } + public bool RotationX { get => Flags.HasFlag(WadAnimRootMotionFlags.RotationX); set => SetFlag(WadAnimRootMotionFlags.RotationX, value); } + public bool RotationY { get => Flags.HasFlag(WadAnimRootMotionFlags.RotationY); set => SetFlag(WadAnimRootMotionFlags.RotationY, value); } + public bool RotationZ { get => Flags.HasFlag(WadAnimRootMotionFlags.RotationZ); set => SetFlag(WadAnimRootMotionFlags.RotationZ, value); } + + private void SetFlag(WadAnimRootMotionFlags flag, bool enabled) + { + if (enabled) + Flags |= flag; + else + Flags &= ~flag; + } + } + public class WadAnimation { public byte FrameRate { get; set; } = 1; @@ -24,6 +58,9 @@ public class WadAnimation public ushort BlendFrameCount { get; set; } public BezierCurve2 BlendCurve { get; set; } = BezierCurve2.Linear.Clone(); + // Root motion settings (TEN only). + public WadAnimRootMotionSettings RootMotion { get; set; } + public List KeyFrames { get; private set; } = new List(); public List StateChanges { get; private set; } = new List(); public List AnimCommands { get; private set; } = new List(); diff --git a/WadTool/AnimBlendPreviewState.cs b/WadTool/AnimBlendPreviewState.cs new file mode 100644 index 0000000000..9189682f35 --- /dev/null +++ b/WadTool/AnimBlendPreviewState.cs @@ -0,0 +1,184 @@ +using System; +using System.Numerics; +using TombLib.Graphics; +using TombLib.Types; + +namespace WadTool +{ + public class AnimBlendPreviewState + { + public bool IsActive { get; private set; } + + private int _frameNumber; + private int _frameCount; + private BezierCurve2 _curve; + private KeyFrame _sourceFrame; + + // Pending blend params set from an incoming state change or next-anim transition. + private int _pendingFrameCount; + private BezierCurve2 _pendingCurve; + + public void SetPendingBlend(int frameCount, BezierCurve2 curve) + { + _pendingFrameCount = frameCount; + _pendingCurve = curve; + } + + public void ClearPendingBlend() + { + _pendingFrameCount = 0; + _pendingCurve = null; + } + + // Captures the source frame from the outgoing animation and starts blending. + // Uses pending blend params if set; falls back to those on the outgoing animation. + // Clears the pending params afterwards. + public bool TryBegin(AnimationNode outgoingAnim, int frameCount, bool smoothAnimation) + { + int blendFrameCount = _pendingFrameCount > 0 ? _pendingFrameCount : outgoingAnim.WadAnimation.BlendFrameCount; + var blendCurve = _pendingFrameCount > 0 ? _pendingCurve : outgoingAnim.WadAnimation.BlendCurve; + + ClearPendingBlend(); + + if (blendFrameCount <= 0) + return false; + + var sourceFrame = CaptureCurrentFrame(outgoingAnim, frameCount, smoothAnimation); + if (sourceFrame == null) + return false; + + _sourceFrame = sourceFrame; + _frameCount = blendFrameCount; + _curve = blendCurve ?? BezierCurve2.Linear; + _frameNumber = 0; + IsActive = true; + return true; + } + + public void Clear() + { + IsActive = false; + _frameNumber = 0; + _frameCount = 0; + _sourceFrame = null; + _curve = null; + ClearPendingBlend(); + } + + // Build the blended pose for the current tick, sampling the target from the given animation. + public void BuildPose(AnimatedModel model, AnimationNode targetAnim, int frameCount, bool smoothAnimation) + { + int frameRate = targetAnim.WadAnimation.FrameRate; + if (frameRate == 0) + frameRate = 1; + + int keyFrameIndex = frameCount / frameRate; + int maxKfIndex = targetAnim.DirectXAnimation.KeyFrames.Count - 1; + keyFrameIndex = Math.Min(keyFrameIndex, maxKfIndex); + + var targetFrame = targetAnim.DirectXAnimation.KeyFrames[keyFrameIndex]; + + if (smoothAnimation && keyFrameIndex < maxKfIndex) + { + float targetK = ((float)frameCount / (float)frameRate) - (float)keyFrameIndex; + targetK = Math.Min(targetK, 1.0f); + + var nextFrame = targetAnim.DirectXAnimation.KeyFrames[keyFrameIndex + 1]; + BuildBlendedPose(model, targetFrame, nextFrame, targetK); + } + else + { + BuildBlendedPose(model, targetFrame); + } + } + + // Advance one frame. Returns false when blending is complete. + public bool Advance() + { + _frameNumber++; + if (_frameNumber >= _frameCount) + { + Clear(); + return false; + } + + return true; + } + + // Get the current blend alpha (0 = fully source, 1 = fully target). + private float GetAlpha() + { + if (_frameCount == 0) + return 0.0f; + + if (_frameCount == 1) + return 1.0f; + + float curveX = (float)_frameNumber / (float)(_frameCount - 1); + return _curve.GetY(curveX); + } + + private void BuildBlendedPose(AnimatedModel model, KeyFrame targetFrame) + { + model.BuildAnimationPose(_sourceFrame, targetFrame, GetAlpha()); + } + + private void BuildBlendedPose(AnimatedModel model, KeyFrame targetFrame1, KeyFrame targetFrame2, float targetK) + { + if (targetK <= 0 || targetFrame1 == targetFrame2) + { + model.BuildAnimationPose(_sourceFrame, targetFrame1, GetAlpha()); + return; + } + + model.BuildAnimationPose(_sourceFrame, InterpolateKeyFrames(targetFrame1, targetFrame2, targetK), GetAlpha()); + } + + // Capture the current animation frame, optionally accounting for smooth interpolation. + public static KeyFrame CaptureCurrentFrame(AnimationNode currentAnim, int frameCount, bool smoothAnimation) + { + if (currentAnim == null || currentAnim.DirectXAnimation.KeyFrames.Count == 0) + return null; + + int frameRate = currentAnim.WadAnimation.FrameRate; + if (frameRate == 0) + frameRate = 1; + + int realFrame = Math.Max(0, frameCount - 1); + int keyFrameIndex = realFrame / frameRate; + int maxKfIndex = currentAnim.DirectXAnimation.KeyFrames.Count - 1; + keyFrameIndex = Math.Min(keyFrameIndex, maxKfIndex); + + if (smoothAnimation && keyFrameIndex < maxKfIndex) + { + float k = (float)realFrame / (float)frameRate - (float)keyFrameIndex; + k = Math.Min(k, 1.0f); + + return InterpolateKeyFrames( + currentAnim.DirectXAnimation.KeyFrames[keyFrameIndex], + currentAnim.DirectXAnimation.KeyFrames[keyFrameIndex + 1], k); + } + + return currentAnim.DirectXAnimation.KeyFrames[keyFrameIndex].Clone(); + } + + // Produce a new KeyFrame by interpolating two source frames. + public static KeyFrame InterpolateKeyFrames(KeyFrame frame1, KeyFrame frame2, float k) + { + var result = new KeyFrame(); + int boneCount = Math.Min(frame1.Quaternions.Count, frame2.Quaternions.Count); + + for (int i = 0; i < boneCount; i++) + { + result.Quaternions.Add(Quaternion.Slerp(frame1.Quaternions[i], frame2.Quaternions[i], k)); + result.Rotations.Add(Vector3.Lerp(frame1.Rotations[i], frame2.Rotations[i], k)); + + var trans = Vector3.Lerp(frame1.Translations[i], frame2.Translations[i], k); + result.Translations.Add(trans); + result.TranslationsMatrices.Add(Matrix4x4.CreateTranslation(trans)); + } + + return result; + } + } +} diff --git a/WadTool/Controls/PanelRenderingAnimationEditor.cs b/WadTool/Controls/PanelRenderingAnimationEditor.cs index c4a935a4c6..73b68af212 100644 --- a/WadTool/Controls/PanelRenderingAnimationEditor.cs +++ b/WadTool/Controls/PanelRenderingAnimationEditor.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Numerics; using System.Windows.Forms; using TombLib; @@ -55,6 +54,9 @@ public ObjectMesh SelectedMesh } private ObjectMesh _selectedMesh; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool DisablePicking { get; set; } + // General state private AnimationEditor _editor; @@ -357,10 +359,10 @@ protected override void OnMouseDown(MouseEventArgs e) if (e.Button == MouseButtons.Left) { - var ray = Ray.GetPickRay(Camera, ClientSize, e.X, e.Y); - - if (_editor.ValidAnimationAndFrames) + if (_editor.ValidAnimationAndFrames && !DisablePicking) { + var ray = Ray.GetPickRay(Camera, ClientSize, e.X, e.Y); + // Try to do gizmo picking if (Configuration.AnimationEditor_ShowGizmo) { diff --git a/WadTool/Forms/FormAnimationEditor.Designer.cs b/WadTool/Forms/FormAnimationEditor.Designer.cs index 8a78d25b49..8aa313837b 100644 --- a/WadTool/Forms/FormAnimationEditor.Designer.cs +++ b/WadTool/Forms/FormAnimationEditor.Designer.cs @@ -71,7 +71,7 @@ private void InitializeComponent() drawGizmoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); drawGridToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); drawCollisionBoxToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - drawSkinToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); // da develop + drawSkinToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); toolStripSeparator9 = new System.Windows.Forms.ToolStripSeparator(); smoothAnimationsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); scrollGridToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -123,15 +123,15 @@ private void InitializeComponent() butAddNewAnimation = new DarkUI.Controls.DarkButton(); panelRendering = new Controls.PanelRenderingAnimationEditor(); darkSectionPanel2 = new DarkUI.Controls.DarkSectionPanel(); + dgvBoundingMeshList = new DarkUI.Controls.DarkDataGridView(); + dgvBoundingMeshListCheckboxes = new DarkUI.Controls.DarkDataGridViewCheckBoxColumn(); + dgvBoundingMeshListMeshes = new System.Windows.Forms.DataGridViewTextBoxColumn(); panelRootMotion = new DarkUI.Controls.DarkPanel(); + cbRootPosX = new DarkUI.Controls.DarkCheckBox(); cbRootPosZ = new DarkUI.Controls.DarkCheckBox(); darkLabel11 = new DarkUI.Controls.DarkLabel(); cbRootRotation = new DarkUI.Controls.DarkCheckBox(); - cbRootPosX = new DarkUI.Controls.DarkCheckBox(); cbRootPosY = new DarkUI.Controls.DarkCheckBox(); - dgvBoundingMeshList = new DarkUI.Controls.DarkDataGridView(); - dgvBoundingMeshListCheckboxes = new DarkUI.Controls.DarkDataGridViewCheckBoxColumn(); - dgvBoundingMeshListMeshes = new System.Windows.Forms.DataGridViewTextBoxColumn(); darkLabel33 = new DarkUI.Controls.DarkLabel(); darkLabel30 = new DarkUI.Controls.DarkLabel(); darkLabel34 = new DarkUI.Controls.DarkLabel(); @@ -211,7 +211,6 @@ private void InitializeComponent() bezierCurveEditor = new Controls.BezierCurveEditor(); darkLabel36 = new DarkUI.Controls.DarkLabel(); cbBlendPreset = new DarkUI.Controls.DarkComboBox(); - darkLabel13 = new DarkUI.Controls.DarkLabel(); nudBlendFrameCount = new DarkUI.Controls.DarkNumericUpDown(); darkLabel12 = new DarkUI.Controls.DarkLabel(); bcAnimation = new Controls.BezierCurveEditor(); @@ -231,8 +230,8 @@ private void InitializeComponent() topBar.SuspendLayout(); darkSectionPanel1.SuspendLayout(); darkSectionPanel2.SuspendLayout(); - panelRootMotion.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)dgvBoundingMeshList).BeginInit(); + panelRootMotion.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)nudBBoxMaxY).BeginInit(); ((System.ComponentModel.ISupportInitialize)nudBBoxMaxZ).BeginInit(); ((System.ComponentModel.ISupportInitialize)nudBBoxMaxX).BeginInit(); @@ -292,7 +291,6 @@ private void InitializeComponent() fileeToolStripMenuItem.Name = "fileeToolStripMenuItem"; fileeToolStripMenuItem.Size = new System.Drawing.Size(39, 20); fileeToolStripMenuItem.Text = "Edit"; - // // undoToolStripMenuItem // @@ -717,7 +715,7 @@ private void InitializeComponent() // renderingToolStripMenuItem // renderingToolStripMenuItem.BackColor = System.Drawing.Color.FromArgb(60, 63, 65); - renderingToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { drawGizmoToolStripMenuItem, drawGridToolStripMenuItem, drawCollisionBoxToolStripMenuItem, toolStripSeparator9, smoothAnimationsToolStripMenuItem, scrollGridToolStripMenuItem, restoreGridHeightToolStripMenuItem }); + renderingToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { drawGizmoToolStripMenuItem, drawGridToolStripMenuItem, drawCollisionBoxToolStripMenuItem, drawSkinToolStripMenuItem, toolStripSeparator9, smoothAnimationsToolStripMenuItem, scrollGridToolStripMenuItem, restoreGridHeightToolStripMenuItem }); renderingToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); renderingToolStripMenuItem.Name = "renderingToolStripMenuItem"; renderingToolStripMenuItem.Size = new System.Drawing.Size(73, 20); @@ -731,7 +729,7 @@ private void InitializeComponent() drawGizmoToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; drawGizmoToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); drawGizmoToolStripMenuItem.Name = "drawGizmoToolStripMenuItem"; - drawGizmoToolStripMenuItem.Size = new System.Drawing.Size(174, 22); + drawGizmoToolStripMenuItem.Size = new System.Drawing.Size(177, 22); drawGizmoToolStripMenuItem.Text = "Draw gizmo"; drawGizmoToolStripMenuItem.Click += drawGizmoToolStripMenuItem_Click; // @@ -743,7 +741,7 @@ private void InitializeComponent() drawGridToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; drawGridToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); drawGridToolStripMenuItem.Name = "drawGridToolStripMenuItem"; - drawGridToolStripMenuItem.Size = new System.Drawing.Size(174, 22); + drawGridToolStripMenuItem.Size = new System.Drawing.Size(177, 22); drawGridToolStripMenuItem.Text = "Draw grid"; drawGridToolStripMenuItem.Click += drawGridToolStripMenuItem_Click; // @@ -755,17 +753,27 @@ private void InitializeComponent() drawCollisionBoxToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; drawCollisionBoxToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); drawCollisionBoxToolStripMenuItem.Name = "drawCollisionBoxToolStripMenuItem"; - drawCollisionBoxToolStripMenuItem.Size = new System.Drawing.Size(174, 22); + drawCollisionBoxToolStripMenuItem.Size = new System.Drawing.Size(177, 22); drawCollisionBoxToolStripMenuItem.Text = "Draw collision box"; drawCollisionBoxToolStripMenuItem.Click += drawCollisionBoxToolStripMenuItem_Click; // + // drawSkinToolStripMenuItem + // + drawSkinToolStripMenuItem.BackColor = System.Drawing.Color.FromArgb(60, 63, 65); + drawSkinToolStripMenuItem.CheckOnClick = true; + drawSkinToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + drawSkinToolStripMenuItem.Name = "drawSkinToolStripMenuItem"; + drawSkinToolStripMenuItem.Size = new System.Drawing.Size(177, 22); + drawSkinToolStripMenuItem.Text = "Draw skinned mesh"; + drawSkinToolStripMenuItem.Click += drawSkinToolStripMenuItem_Click; + // // toolStripSeparator9 // toolStripSeparator9.BackColor = System.Drawing.Color.FromArgb(60, 63, 65); toolStripSeparator9.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); toolStripSeparator9.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1); toolStripSeparator9.Name = "toolStripSeparator9"; - toolStripSeparator9.Size = new System.Drawing.Size(171, 6); + toolStripSeparator9.Size = new System.Drawing.Size(174, 6); // // smoothAnimationsToolStripMenuItem // @@ -775,7 +783,7 @@ private void InitializeComponent() smoothAnimationsToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; smoothAnimationsToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); smoothAnimationsToolStripMenuItem.Name = "smoothAnimationsToolStripMenuItem"; - smoothAnimationsToolStripMenuItem.Size = new System.Drawing.Size(174, 22); + smoothAnimationsToolStripMenuItem.Size = new System.Drawing.Size(177, 22); smoothAnimationsToolStripMenuItem.Text = "Smooth animation"; smoothAnimationsToolStripMenuItem.Click += smoothAnimationsToolStripMenuItem_Click; // @@ -787,7 +795,7 @@ private void InitializeComponent() scrollGridToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; scrollGridToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); scrollGridToolStripMenuItem.Name = "scrollGridToolStripMenuItem"; - scrollGridToolStripMenuItem.Size = new System.Drawing.Size(174, 22); + scrollGridToolStripMenuItem.Size = new System.Drawing.Size(177, 22); scrollGridToolStripMenuItem.Text = "Scroll grid"; scrollGridToolStripMenuItem.Click += scrollGridToolStripMenuItem_Click; // @@ -797,7 +805,7 @@ private void InitializeComponent() restoreGridHeightToolStripMenuItem.CheckOnClick = true; restoreGridHeightToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); restoreGridHeightToolStripMenuItem.Name = "restoreGridHeightToolStripMenuItem"; - restoreGridHeightToolStripMenuItem.Size = new System.Drawing.Size(174, 22); + restoreGridHeightToolStripMenuItem.Size = new System.Drawing.Size(177, 22); restoreGridHeightToolStripMenuItem.Text = "Restore grid height"; restoreGridHeightToolStripMenuItem.Click += restoreGridHeightToolStripMenuItem_Click; // @@ -1233,7 +1241,7 @@ private void InitializeComponent() lstAnimations.MouseWheelScrollSpeedV = 0.2F; lstAnimations.MultiSelect = true; lstAnimations.Name = "lstAnimations"; - lstAnimations.Size = new System.Drawing.Size(271, 187); + lstAnimations.Size = new System.Drawing.Size(271, 216); lstAnimations.TabIndex = 3; lstAnimations.SelectedIndicesChanged += lstAnimations_SelectedIndicesChanged; lstAnimations.Click += lstAnimations_Click; @@ -1252,7 +1260,7 @@ private void InitializeComponent() darkSectionPanel1.MinimumSize = new System.Drawing.Size(280, 120); darkSectionPanel1.Name = "darkSectionPanel1"; darkSectionPanel1.SectionHeader = "Animation List"; - darkSectionPanel1.Size = new System.Drawing.Size(280, 272); + darkSectionPanel1.Size = new System.Drawing.Size(280, 301); darkSectionPanel1.TabIndex = 9; // // butShowAll @@ -1272,7 +1280,7 @@ private void InitializeComponent() butDeleteAnimation.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; butDeleteAnimation.Checked = false; butDeleteAnimation.Image = (System.Drawing.Image)resources.GetObject("butDeleteAnimation.Image"); - butDeleteAnimation.Location = new System.Drawing.Point(252, 244); + butDeleteAnimation.Location = new System.Drawing.Point(252, 273); butDeleteAnimation.Name = "butDeleteAnimation"; butDeleteAnimation.Size = new System.Drawing.Size(23, 24); butDeleteAnimation.TabIndex = 5; @@ -1296,7 +1304,7 @@ private void InitializeComponent() butAddNewAnimation.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; butAddNewAnimation.Checked = false; butAddNewAnimation.Image = (System.Drawing.Image)resources.GetObject("butAddNewAnimation.Image"); - butAddNewAnimation.Location = new System.Drawing.Point(225, 244); + butAddNewAnimation.Location = new System.Drawing.Point(225, 273); butAddNewAnimation.Name = "butAddNewAnimation"; butAddNewAnimation.Size = new System.Drawing.Size(23, 24); butAddNewAnimation.TabIndex = 4; @@ -1326,12 +1334,47 @@ private void InitializeComponent() darkSectionPanel2.Size = new System.Drawing.Size(280, 386); darkSectionPanel2.TabIndex = 6; // + // dgvBoundingMeshList + // + dgvBoundingMeshList.AllowUserToAddRows = false; + dgvBoundingMeshList.AllowUserToDeleteRows = false; + dgvBoundingMeshList.AllowUserToDragDropRows = false; + dgvBoundingMeshList.AllowUserToPasteCells = false; + dgvBoundingMeshList.AllowUserToResizeColumns = false; + dgvBoundingMeshList.ColumnHeadersHeight = 17; + dgvBoundingMeshList.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { dgvBoundingMeshListCheckboxes, dgvBoundingMeshListMeshes }); + dgvBoundingMeshList.Dock = System.Windows.Forms.DockStyle.Fill; + dgvBoundingMeshList.ForegroundColor = System.Drawing.Color.FromArgb(220, 220, 220); + dgvBoundingMeshList.Location = new System.Drawing.Point(1, 25); + dgvBoundingMeshList.MultiSelect = false; + dgvBoundingMeshList.Name = "dgvBoundingMeshList"; + dgvBoundingMeshList.RowHeadersWidth = 41; + dgvBoundingMeshList.Size = new System.Drawing.Size(278, 328); + dgvBoundingMeshList.TabIndex = 25; + dgvBoundingMeshList.CellMouseDoubleClick += dgvBoundingMeshList_CellMouseDoubleClick; + dgvBoundingMeshList.SelectionChanged += dgvBoundingMeshList_SelectionChanged; + // + // dgvBoundingMeshListCheckboxes + // + dgvBoundingMeshListCheckboxes.HeaderText = "Use"; + dgvBoundingMeshListCheckboxes.Name = "dgvBoundingMeshListCheckboxes"; + dgvBoundingMeshListCheckboxes.Resizable = System.Windows.Forms.DataGridViewTriState.False; + dgvBoundingMeshListCheckboxes.Width = 40; + // + // dgvBoundingMeshListMeshes + // + dgvBoundingMeshListMeshes.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + dgvBoundingMeshListMeshes.HeaderText = "Mesh"; + dgvBoundingMeshListMeshes.Name = "dgvBoundingMeshListMeshes"; + dgvBoundingMeshListMeshes.ReadOnly = true; + dgvBoundingMeshListMeshes.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // // panelRootMotion // + panelRootMotion.Controls.Add(cbRootPosX); panelRootMotion.Controls.Add(cbRootPosZ); panelRootMotion.Controls.Add(darkLabel11); panelRootMotion.Controls.Add(cbRootRotation); - panelRootMotion.Controls.Add(cbRootPosX); panelRootMotion.Controls.Add(cbRootPosY); panelRootMotion.Dock = System.Windows.Forms.DockStyle.Bottom; panelRootMotion.Location = new System.Drawing.Point(1, 353); @@ -1339,21 +1382,32 @@ private void InitializeComponent() panelRootMotion.Size = new System.Drawing.Size(278, 32); panelRootMotion.TabIndex = 26; // + // cbRootPosX + // + cbRootPosX.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + cbRootPosX.AutoSize = true; + cbRootPosX.Location = new System.Drawing.Point(86, 9); + cbRootPosX.Name = "cbRootPosX"; + cbRootPosX.Size = new System.Drawing.Size(32, 17); + cbRootPosX.TabIndex = 107; + cbRootPosX.Text = "X"; + // // cbRootPosZ // cbRootPosZ.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; cbRootPosZ.AutoSize = true; - cbRootPosZ.Location = new System.Drawing.Point(168, 9); + cbRootPosZ.Location = new System.Drawing.Point(161, 9); cbRootPosZ.Name = "cbRootPosZ"; cbRootPosZ.Size = new System.Drawing.Size(32, 17); cbRootPosZ.TabIndex = 101; cbRootPosZ.Text = "Z"; + cbRootPosZ.CheckedChanged += cbRootPosZ_CheckedChanged; // // darkLabel11 // darkLabel11.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; darkLabel11.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - darkLabel11.Location = new System.Drawing.Point(4, 10); + darkLabel11.Location = new System.Drawing.Point(1, 10); darkLabel11.Name = "darkLabel11"; darkLabel11.Size = new System.Drawing.Size(76, 13); darkLabel11.TabIndex = 106; @@ -1363,67 +1417,23 @@ private void InitializeComponent() // cbRootRotation.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; cbRootRotation.AutoSize = true; - cbRootRotation.Location = new System.Drawing.Point(206, 9); + cbRootRotation.Location = new System.Drawing.Point(201, 9); cbRootRotation.Name = "cbRootRotation"; cbRootRotation.Size = new System.Drawing.Size(71, 17); cbRootRotation.TabIndex = 102; cbRootRotation.Text = "Rotation"; - // - // cbRootPosX - // - cbRootPosX.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; - cbRootPosX.AutoSize = true; - cbRootPosX.Location = new System.Drawing.Point(91, 9); - cbRootPosX.Name = "cbRootPosX"; - cbRootPosX.Size = new System.Drawing.Size(32, 17); - cbRootPosX.TabIndex = 99; - cbRootPosX.Text = "X"; - cbRootPosX.CheckedChanged += cbRootPosX_CheckedChanged; + cbRootRotation.CheckedChanged += cbRootRotation_CheckedChanged; // // cbRootPosY // cbRootPosY.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; cbRootPosY.AutoSize = true; - cbRootPosY.Location = new System.Drawing.Point(131, 9); + cbRootPosY.Location = new System.Drawing.Point(124, 9); cbRootPosY.Name = "cbRootPosY"; cbRootPosY.Size = new System.Drawing.Size(31, 17); cbRootPosY.TabIndex = 100; cbRootPosY.Text = "Y"; - // - // dgvBoundingMeshList - // - dgvBoundingMeshList.AllowUserToAddRows = false; - dgvBoundingMeshList.AllowUserToDeleteRows = false; - dgvBoundingMeshList.AllowUserToDragDropRows = false; - dgvBoundingMeshList.AllowUserToPasteCells = false; - dgvBoundingMeshList.AllowUserToResizeColumns = false; - dgvBoundingMeshList.ColumnHeadersHeight = 17; - dgvBoundingMeshList.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { dgvBoundingMeshListCheckboxes, dgvBoundingMeshListMeshes }); - dgvBoundingMeshList.Dock = System.Windows.Forms.DockStyle.Fill; - dgvBoundingMeshList.ForegroundColor = System.Drawing.Color.FromArgb(220, 220, 220); - dgvBoundingMeshList.Location = new System.Drawing.Point(1, 25); - dgvBoundingMeshList.MultiSelect = false; - dgvBoundingMeshList.Name = "dgvBoundingMeshList"; - dgvBoundingMeshList.RowHeadersWidth = 41; - dgvBoundingMeshList.Size = new System.Drawing.Size(278, 328); - dgvBoundingMeshList.TabIndex = 25; - dgvBoundingMeshList.CellMouseDoubleClick += dgvBoundingMeshList_CellMouseDoubleClick; - dgvBoundingMeshList.SelectionChanged += dgvBoundingMeshList_SelectionChanged; - // - // dgvBoundingMeshListCheckboxes - // - dgvBoundingMeshListCheckboxes.HeaderText = "Use"; - dgvBoundingMeshListCheckboxes.Name = "dgvBoundingMeshListCheckboxes"; - dgvBoundingMeshListCheckboxes.Resizable = System.Windows.Forms.DataGridViewTriState.False; - dgvBoundingMeshListCheckboxes.Width = 40; - // - // dgvBoundingMeshListMeshes - // - dgvBoundingMeshListMeshes.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; - dgvBoundingMeshListMeshes.HeaderText = "Mesh"; - dgvBoundingMeshListMeshes.Name = "dgvBoundingMeshListMeshes"; - dgvBoundingMeshListMeshes.ReadOnly = true; - dgvBoundingMeshListMeshes.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + cbRootPosY.CheckedChanged += cbRootPosY_CheckedChanged; // // darkLabel33 // @@ -1892,7 +1902,7 @@ private void InitializeComponent() darkSectionPanel4.Controls.Add(darkLabel6); darkSectionPanel4.Controls.Add(darkLabel7); darkSectionPanel4.Dock = System.Windows.Forms.DockStyle.Bottom; - darkSectionPanel4.Location = new System.Drawing.Point(0, 272); + darkSectionPanel4.Location = new System.Drawing.Point(0, 301); darkSectionPanel4.MaximumSize = new System.Drawing.Size(280, 238); darkSectionPanel4.Name = "darkSectionPanel4"; darkSectionPanel4.SectionHeader = "Current Animation"; @@ -2370,22 +2380,22 @@ private void InitializeComponent() sectionBlending.Controls.Add(bezierCurveEditor); sectionBlending.Controls.Add(darkLabel36); sectionBlending.Controls.Add(cbBlendPreset); - sectionBlending.Controls.Add(darkLabel13); sectionBlending.Controls.Add(nudBlendFrameCount); sectionBlending.Controls.Add(darkLabel12); sectionBlending.Controls.Add(bcAnimation); sectionBlending.Dock = System.Windows.Forms.DockStyle.Bottom; - sectionBlending.Location = new System.Drawing.Point(0, 469); + sectionBlending.Location = new System.Drawing.Point(0, 498); sectionBlending.Name = "sectionBlending"; sectionBlending.SectionHeader = "Animation Blending"; - sectionBlending.Size = new System.Drawing.Size(280, 203); + sectionBlending.Size = new System.Drawing.Size(280, 174); sectionBlending.TabIndex = 128; // // bezierCurveEditor // + bezierCurveEditor.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; bezierCurveEditor.Location = new System.Drawing.Point(6, 57); bezierCurveEditor.Name = "bezierCurveEditor"; - bezierCurveEditor.Size = new System.Drawing.Size(269, 113); + bezierCurveEditor.Size = new System.Drawing.Size(269, 85); bezierCurveEditor.TabIndex = 110; toolTip1.SetToolTip(bezierCurveEditor, "Specify blending curve by dragging handles"); bezierCurveEditor.ValueChanged += bezierCurveEditor_ValueChanged; @@ -2395,7 +2405,7 @@ private void InitializeComponent() darkLabel36.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; darkLabel36.AutoSize = true; darkLabel36.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - darkLabel36.Location = new System.Drawing.Point(3, 179); + darkLabel36.Location = new System.Drawing.Point(3, 150); darkLabel36.Name = "darkLabel36"; darkLabel36.Size = new System.Drawing.Size(41, 13); darkLabel36.TabIndex = 109; @@ -2406,31 +2416,22 @@ private void InitializeComponent() cbBlendPreset.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; cbBlendPreset.FormattingEnabled = true; cbBlendPreset.Items.AddRange(new object[] { "Linear", "Ease In", "Ease Out", "Ease In and Out" }); - cbBlendPreset.Location = new System.Drawing.Point(49, 175); + cbBlendPreset.Location = new System.Drawing.Point(49, 146); cbBlendPreset.Name = "cbBlendPreset"; cbBlendPreset.Size = new System.Drawing.Size(225, 23); cbBlendPreset.TabIndex = 108; toolTip1.SetToolTip(cbBlendPreset, "Predefined curve preset"); cbBlendPreset.SelectedIndexChanged += cbBlendPreset_SelectedIndexChanged; // - // darkLabel13 - // - darkLabel13.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - darkLabel13.Location = new System.Drawing.Point(233, 32); - darkLabel13.Name = "darkLabel13"; - darkLabel13.Size = new System.Drawing.Size(41, 13); - darkLabel13.TabIndex = 107; - darkLabel13.Text = "frames"; - // // nudBlendFrameCount // nudBlendFrameCount.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; nudBlendFrameCount.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - nudBlendFrameCount.Location = new System.Drawing.Point(170, 29); + nudBlendFrameCount.Location = new System.Drawing.Point(211, 30); nudBlendFrameCount.LoopValues = false; nudBlendFrameCount.Maximum = new decimal(new int[] { 255, 0, 0, 0 }); nudBlendFrameCount.Name = "nudBlendFrameCount"; - nudBlendFrameCount.Size = new System.Drawing.Size(61, 22); + nudBlendFrameCount.Size = new System.Drawing.Size(64, 22); nudBlendFrameCount.TabIndex = 97; toolTip1.SetToolTip(nudBlendFrameCount, "Blending duration to the next animation in frames"); nudBlendFrameCount.ValueChanged += nudBlendFrameCount_ValueChanged; @@ -2442,7 +2443,7 @@ private void InitializeComponent() darkLabel12.Name = "darkLabel12"; darkLabel12.Size = new System.Drawing.Size(162, 13); darkLabel12.TabIndex = 98; - darkLabel12.Text = "Next anim blending duration:"; + darkLabel12.Text = "Next anim blend frame count:"; // // bcAnimation // @@ -2569,9 +2570,9 @@ private void InitializeComponent() darkSectionPanel1.ResumeLayout(false); darkSectionPanel1.PerformLayout(); darkSectionPanel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)dgvBoundingMeshList).EndInit(); panelRootMotion.ResumeLayout(false); panelRootMotion.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)dgvBoundingMeshList).EndInit(); ((System.ComponentModel.ISupportInitialize)nudBBoxMaxY).EndInit(); ((System.ComponentModel.ISupportInitialize)nudBBoxMaxZ).EndInit(); ((System.ComponentModel.ISupportInitialize)nudBBoxMaxX).EndInit(); @@ -2802,12 +2803,10 @@ private void InitializeComponent() private DarkUI.Controls.DarkLabel darkLabel11; private DarkUI.Controls.DarkCheckBox cbRootPosZ; private DarkUI.Controls.DarkNumericUpDown nudBlendFrameCount; - private DarkUI.Controls.DarkCheckBox cbRootPosX; private DarkUI.Controls.DarkLabel darkLabel12; private DarkUI.Controls.DarkCheckBox cbRootPosY; private DarkUI.Controls.DarkCheckBox cbRootRotation; private Controls.BezierCurveEditor bcAnimation; - private DarkUI.Controls.DarkLabel darkLabel13; private DarkUI.Controls.DarkLabel darkLabel14; private DarkUI.Controls.DarkLabel darkLabel15; private DarkUI.Controls.DarkLabel darkLabel33; @@ -2826,5 +2825,6 @@ private void InitializeComponent() private Controls.BezierCurveEditor bezierCurveEditor; private DarkUI.Controls.DarkPanel panelRootMotion; private System.Windows.Forms.ToolStripMenuItem drawSkinToolStripMenuItem; + private DarkUI.Controls.DarkCheckBox cbRootPosX; } } \ No newline at end of file diff --git a/WadTool/Forms/FormAnimationEditor.cs b/WadTool/Forms/FormAnimationEditor.cs index fa333693df..d07ca08321 100644 --- a/WadTool/Forms/FormAnimationEditor.cs +++ b/WadTool/Forms/FormAnimationEditor.cs @@ -67,11 +67,15 @@ private bool Saved private int _chainedPlaybackInitialAnim; private int _chainedPlaybackInitialCursorPos; private VectorInt2 _chainedPlaybackInitialSelection; + // State change vars private int _chainedPlaybackIncomingAnimation = -1; private int _chainedPlaybackIncomingFrame = -1; private VectorInt2 _chainedPlaybackIncomingFrameRange = -VectorInt2.One; + // Blend preview + private readonly AnimBlendPreviewState _blendState = new AnimBlendPreviewState(); + // Live update flag, used when transform is updated during playback or scrubbling private bool _allowUpdate = true; @@ -94,9 +98,8 @@ public FormAnimationEditor(WadToolClass tool, DeviceManager deviceManager, Wad2 if (!isTEN && _editor.Tool.Configuration.AnimationEditor_SoundPreviewType > SoundPreviewType.Water) _editor.Tool.Configuration.AnimationEditor_SoundPreviewType = SoundPreviewType.Land; - // TODO: Unlock when anim blending is finished. - sectionBlending.Visible = false; //isTEN; - panelRootMotion.Visible = false; //isTEN; + sectionBlending.Visible = isTEN; + panelRootMotion.Visible = isTEN; // Update UI UpdateUIControls(); @@ -239,6 +242,7 @@ obj is WadToolClass.AnimationEditorCurrentAnimationChangedEvent || _chainedPlaybackIncomingAnimation = e.NextAnimation; _chainedPlaybackIncomingFrame = e.NextFrame; _chainedPlaybackIncomingFrameRange = e.FrameRange; + _blendState.SetPendingBlend(e.BlendFrames, e.BlendCurve); } } @@ -503,6 +507,11 @@ private void SelectAnimation(AnimationNode node) bezierCurveEditor.Value = node.WadAnimation.BlendCurve; cbBlendPreset.SelectedIndex = -1; + cbRootPosX.Checked = node.WadAnimation.RootMotion.TranslationX; + cbRootPosY.Checked = node.WadAnimation.RootMotion.TranslationY; + cbRootPosZ.Checked = node.WadAnimation.RootMotion.TranslationZ; + cbRootRotation.Checked = node.WadAnimation.RootMotion.RotationY; + tbStateId.Text = node.WadAnimation.StateId.ToString(); UpdateStateChange(); } @@ -734,6 +743,10 @@ public void UpdateTransform() { if (!_allowUpdate || _editor.CurrentKeyFrame == null) return; + // Block modifications during blend preview. + if (_blendState.IsActive && _editor.Tool.Configuration.AnimationEditor_ChainPlayback) + return; + var meshIndex = panelRendering.SelectedMesh == null ? 0 : panelRendering.Model.Meshes.IndexOf(panelRendering.SelectedMesh); _editor.UpdateTransform(meshIndex, @@ -1826,6 +1839,10 @@ private void PlayAnimation() } butTransportPlay.Image = Properties.Resources.transport_play_24; + + // Clear blend preview state. + _blendState.Clear(); + panelRendering.DisablePicking = false; } // Reset grid position and refresh view @@ -2303,7 +2320,7 @@ private void cmCreateStateChangeMenuItem_Click(object sender, EventArgs e) InFrame = (ushort)timeline.SelectionStartFrameIndex, OutFrame = (ushort)timeline.SelectionEndFrameIndex, NextAnimation = 0, - NextFrameLow = 0 + NextLowFrame = 0 }); } @@ -2350,6 +2367,7 @@ private void timerPlayAnimation_Tick(object sender, EventArgs e) { popup.ShowError(panelRendering, "Pending state change to animation #" + _chainedPlaybackIncomingAnimation + " had incorrect data and was ignored."); _chainedPlaybackIncomingAnimation = -1; + _blendState.ClearPendingBlend(); } } @@ -2358,6 +2376,10 @@ private void timerPlayAnimation_Tick(object sender, EventArgs e) // Chain playback handling if (_editor.Tool.Configuration.AnimationEditor_ChainPlayback) { + // Begin blend preview before switching animations. + bool blendStarted = _blendState.TryBegin(_editor.CurrentAnim, _frameCount, _editor.Tool.Configuration.AnimationEditor_SmoothAnimation); + panelRendering.DisablePicking = blendStarted; + // Clear state change anim, as we don't need it anymore _chainedPlaybackIncomingAnimation = -1; @@ -2496,6 +2518,16 @@ private void timerPlayAnimation_Tick(object sender, EventArgs e) SelectFrame(k); } + // Apply blend preview if active. + if (_blendState.IsActive) + { + _blendState.BuildPose(panelRendering.Model, _editor.CurrentAnim, _frameCount, _editor.Tool.Configuration.AnimationEditor_SmoothAnimation); + panelRendering.Invalidate(); + + if (!_blendState.Advance()) + panelRendering.DisablePicking = false; + } + UpdateStatusLabel(); } @@ -2836,9 +2868,30 @@ private void SetCurrentTransportPreviewType(SoundPreviewType previewType) UpdateUIControls(); } - private void cbRootPosX_CheckedChanged(object sender, EventArgs e) + private void cbRootPosX_CheckedChanged(object sender, EventArgs e) => UpdateRootMotionSetting(sender); + private void cbRootPosY_CheckedChanged(object sender, EventArgs e) => UpdateRootMotionSetting(sender); + private void cbRootPosZ_CheckedChanged(object sender, EventArgs e) => UpdateRootMotionSetting(sender); + private void cbRootRotation_CheckedChanged(object sender, EventArgs e) => UpdateRootMotionSetting(sender); + + private void UpdateRootMotionSetting(object sender) { - // TODO: Where to put these flags? + if (!_allowUpdate || _editor.CurrentAnim == null) + return; + + if (!_editor.MadeChanges) + { + _editor.Tool.UndoManager.PushAnimationChanged(_editor, _editor.CurrentAnim); + _editor.MadeChanges = true; + } + + var rootMotion = _editor.CurrentAnim.WadAnimation.RootMotion; + rootMotion.TranslationX = cbRootPosX.Checked; + rootMotion.TranslationY = cbRootPosY.Checked; + rootMotion.TranslationZ = cbRootPosZ.Checked; + rootMotion.RotationY = cbRootRotation.Checked; + _editor.CurrentAnim.WadAnimation.RootMotion = rootMotion; + + Saved = false; } private void cbBlendPreset_SelectedIndexChanged(object sender, EventArgs e) diff --git a/WadTool/Forms/FormAnimationFixer.cs b/WadTool/Forms/FormAnimationFixer.cs index 4eb738eed6..46990ba772 100644 --- a/WadTool/Forms/FormAnimationFixer.cs +++ b/WadTool/Forms/FormAnimationFixer.cs @@ -27,7 +27,7 @@ public FormAnimationFixer(AnimationEditor editor, List animations Configuration.ConfigureWindow(this, _editor.Tool.Configuration); } - private bool FixAnimation(AnimationNode animation, bool fixEndFrame, bool fixNextAnim, bool fixNextFrame, bool fixStateChangeRanges, bool fixStateChangeNextAnim, bool fixStateChangeNextFrameLow, bool fixName) + private bool FixAnimation(AnimationNode animation, bool fixEndFrame, bool fixNextAnim, bool fixNextFrame, bool fixStateChangeRanges, bool fixStateChangeNextAnim, bool fixStateChangeNextFrame, bool fixName) { bool anyChange = false; @@ -86,10 +86,10 @@ private bool FixAnimation(AnimationNode animation, bool fixEndFrame, bool fixNex anyChange = true; } - if (fixStateChangeNextFrameLow && - disp.NextAnimation < _editor.Animations.Count && disp.NextFrameLow > _editor.Animations[disp.NextAnimation].WadAnimation.EndFrame) + if (fixStateChangeNextFrame && + disp.NextAnimation < _editor.Animations.Count && disp.NextLowFrame > _editor.Animations[disp.NextAnimation].WadAnimation.EndFrame) { - disp.NextFrameLow = _editor.Animations[disp.NextAnimation].WadAnimation.EndFrame; + disp.NextLowFrame = _editor.Animations[disp.NextAnimation].WadAnimation.EndFrame; anyChange = true; } } diff --git a/WadTool/Forms/FormBlendCurveEditor.Designer.cs b/WadTool/Forms/FormBlendCurveEditor.Designer.cs new file mode 100644 index 0000000000..2bf9b783a1 --- /dev/null +++ b/WadTool/Forms/FormBlendCurveEditor.Designer.cs @@ -0,0 +1,121 @@ +namespace WadTool +{ + partial class FormBlendCurveEditor + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + components.Dispose(); + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + bezierCurveEditor = new Controls.BezierCurveEditor(); + cbBlendPreset = new DarkUI.Controls.DarkComboBox(); + darkLabel1 = new DarkUI.Controls.DarkLabel(); + btOk = new DarkUI.Controls.DarkButton(); + btCancel = new DarkUI.Controls.DarkButton(); + SuspendLayout(); + // + // bezierCurveEditor + // + bezierCurveEditor.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + bezierCurveEditor.Location = new System.Drawing.Point(8, 8); + bezierCurveEditor.Margin = new System.Windows.Forms.Padding(5, 3, 5, 3); + bezierCurveEditor.Name = "bezierCurveEditor"; + bezierCurveEditor.Size = new System.Drawing.Size(248, 230); + bezierCurveEditor.TabIndex = 0; + bezierCurveEditor.ValueChanged += bezierCurveEditor_ValueChanged; + // + // cbBlendPreset + // + cbBlendPreset.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + cbBlendPreset.FormattingEnabled = true; + cbBlendPreset.Items.AddRange(new object[] { "Linear", "Ease In", "Ease Out", "Ease In and Out" }); + cbBlendPreset.Location = new System.Drawing.Point(53, 247); + cbBlendPreset.Name = "cbBlendPreset"; + cbBlendPreset.Size = new System.Drawing.Size(203, 23); + cbBlendPreset.TabIndex = 1; + cbBlendPreset.SelectedIndexChanged += cbBlendPreset_SelectedIndexChanged; + // + // darkLabel1 + // + darkLabel1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + darkLabel1.AutoSize = true; + darkLabel1.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + darkLabel1.Location = new System.Drawing.Point(8, 251); + darkLabel1.Name = "darkLabel1"; + darkLabel1.Size = new System.Drawing.Size(41, 13); + darkLabel1.TabIndex = 2; + darkLabel1.Text = "Preset:"; + // + // btOk + // + btOk.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + btOk.Checked = false; + btOk.DialogResult = System.Windows.Forms.DialogResult.OK; + btOk.Location = new System.Drawing.Point(101, 279); + btOk.Name = "btOk"; + btOk.Size = new System.Drawing.Size(75, 23); + btOk.TabIndex = 3; + btOk.Text = "OK"; + // + // btCancel + // + btCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + btCancel.Checked = false; + btCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + btCancel.Location = new System.Drawing.Point(182, 279); + btCancel.Name = "btCancel"; + btCancel.Size = new System.Drawing.Size(75, 23); + btCancel.TabIndex = 4; + btCancel.Text = "Cancel"; + // + // FormBlendCurveEditor + // + AcceptButton = btOk; + AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + CancelButton = btCancel; + ClientSize = new System.Drawing.Size(264, 310); + Controls.Add(bezierCurveEditor); + Controls.Add(darkLabel1); + Controls.Add(cbBlendPreset); + Controls.Add(btOk); + Controls.Add(btCancel); + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + MaximizeBox = false; + MinimizeBox = false; + Name = "FormBlendCurveEditor"; + ShowIcon = false; + ShowInTaskbar = false; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Blend curve"; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private Controls.BezierCurveEditor bezierCurveEditor; + private DarkUI.Controls.DarkComboBox cbBlendPreset; + private DarkUI.Controls.DarkLabel darkLabel1; + private DarkUI.Controls.DarkButton btOk; + private DarkUI.Controls.DarkButton btCancel; + } +} diff --git a/WadTool/Forms/FormBlendCurveEditor.cs b/WadTool/Forms/FormBlendCurveEditor.cs new file mode 100644 index 0000000000..86ac781b74 --- /dev/null +++ b/WadTool/Forms/FormBlendCurveEditor.cs @@ -0,0 +1,47 @@ +using System; +using TombLib.Types; + +namespace WadTool +{ + public partial class FormBlendCurveEditor : DarkUI.Forms.DarkForm + { + public BezierCurve2 ResultCurve { get; private set; } + + public FormBlendCurveEditor(BezierCurve2 curve) + { + InitializeComponent(); + + ResultCurve = curve.Clone(); + bezierCurveEditor.Value = ResultCurve; + } + + private void cbBlendPreset_SelectedIndexChanged(object sender, EventArgs e) + { + switch (cbBlendPreset.SelectedIndex) + { + case 0: + bezierCurveEditor.Value.Set(BezierCurve2.Linear); + break; + + case 1: + bezierCurveEditor.Value.Set(BezierCurve2.EaseIn); + break; + + case 2: + bezierCurveEditor.Value.Set(BezierCurve2.EaseOut); + break; + + case 3: + bezierCurveEditor.Value.Set(BezierCurve2.EaseInOut); + break; + } + + bezierCurveEditor.UpdateUI(); + } + + private void bezierCurveEditor_ValueChanged(object sender, EventArgs e) + { + cbBlendPreset.SelectedIndex = -1; + } + } +} diff --git a/WadTool/Forms/FormBlendCurveEditor.resx b/WadTool/Forms/FormBlendCurveEditor.resx new file mode 100644 index 0000000000..8b2ff64a11 --- /dev/null +++ b/WadTool/Forms/FormBlendCurveEditor.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/WadTool/Forms/FormStateChangesEditor.Designer.cs b/WadTool/Forms/FormStateChangesEditor.Designer.cs index 861d162f5b..1353d7e5c2 100644 --- a/WadTool/Forms/FormStateChangesEditor.Designer.cs +++ b/WadTool/Forms/FormStateChangesEditor.Designer.cs @@ -1,4 +1,4 @@ -namespace WadTool +namespace WadTool { partial class FormStateChangesEditor { @@ -22,151 +22,169 @@ private void InitializeComponent() columnLowFrame = new System.Windows.Forms.DataGridViewTextBoxColumn(); columnHighFrame = new System.Windows.Forms.DataGridViewTextBoxColumn(); columnNextAnimation = new System.Windows.Forms.DataGridViewTextBoxColumn(); - columnNextFrame = new System.Windows.Forms.DataGridViewTextBoxColumn(); + columnNextLowFrame = new System.Windows.Forms.DataGridViewTextBoxColumn(); + columnNextHighFrame = new System.Windows.Forms.DataGridViewTextBoxColumn(); + columnBlendFrames = new System.Windows.Forms.DataGridViewTextBoxColumn(); + columnBlendCurve = new DarkUI.Controls.DarkDataGridViewButtonColumn(); btCancel = new DarkUI.Controls.DarkButton(); btOk = new DarkUI.Controls.DarkButton(); butPlayStateChange = new DarkUI.Controls.DarkButton(); dgvControls = new TombLib.Controls.DarkDataGridViewControls(); lblStateChangeAnnouncement = new DarkUI.Controls.DarkLabel(); toolTip1 = new System.Windows.Forms.ToolTip(components); - nudBlendEndFrame = new DarkUI.Controls.DarkNumericUpDown(); - nudBlendFrameCount = new DarkUI.Controls.DarkNumericUpDown(); - cbBlendPreset = new DarkUI.Controls.DarkComboBox(); - bezierCurveEditor = new Controls.BezierCurveEditor(); butApply = new DarkUI.Controls.DarkButton(); stateChangeGroup = new DarkUI.Controls.DarkGroupBox(); - blendingGroup = new DarkUI.Controls.DarkGroupBox(); - darkLabel1 = new DarkUI.Controls.DarkLabel(); - darkLabel3 = new DarkUI.Controls.DarkLabel(); - darkLabel2 = new DarkUI.Controls.DarkLabel(); ((System.ComponentModel.ISupportInitialize)dgvStateChanges).BeginInit(); - ((System.ComponentModel.ISupportInitialize)nudBlendEndFrame).BeginInit(); - ((System.ComponentModel.ISupportInitialize)nudBlendFrameCount).BeginInit(); stateChangeGroup.SuspendLayout(); - blendingGroup.SuspendLayout(); SuspendLayout(); - // + // // dgvStateChanges - // + // dgvStateChanges.AllowUserToAddRows = false; dgvStateChanges.AllowUserToDragDropRows = false; dgvStateChanges.AllowUserToPasteCells = false; dgvStateChanges.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; dgvStateChanges.AutoGenerateColumns = false; dgvStateChanges.ColumnHeadersHeight = 17; - dgvStateChanges.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { columnStateName, columnStateId, columnLowFrame, columnHighFrame, columnNextAnimation, columnNextFrame }); + dgvStateChanges.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { columnStateName, columnStateId, columnLowFrame, columnHighFrame, columnNextAnimation, columnNextLowFrame, columnNextHighFrame, columnBlendFrames, columnBlendCurve }); dgvStateChanges.ForegroundColor = System.Drawing.Color.FromArgb(220, 220, 220); dgvStateChanges.Location = new System.Drawing.Point(7, 22); dgvStateChanges.Name = "dgvStateChanges"; dgvStateChanges.RowHeadersWidth = 40; - dgvStateChanges.RowTemplate.Height = 16; + dgvStateChanges.RowTemplate.Height = 20; dgvStateChanges.ShowCellErrors = false; - dgvStateChanges.Size = new System.Drawing.Size(489, 292); + dgvStateChanges.Size = new System.Drawing.Size(789, 292); dgvStateChanges.TabIndex = 48; dgvStateChanges.CellFormattingSafe += dgvStateChanges_CellFormattingSafe; + dgvStateChanges.CellContentClick += dgvStateChanges_CellContentClick; dgvStateChanges.CellEndEdit += dgvStateChanges_CellEndEdit; dgvStateChanges.CellMouseDoubleClick += dgvStateChanges_CellMouseDoubleClick; dgvStateChanges.CellValidating += dgvStateChanges_CellValidating; + dgvStateChanges.CellPainting += dgvStateChanges_CellPainting; dgvStateChanges.SelectionChanged += dgvStateChanges_SelectionChanged; dgvStateChanges.UserDeletedRow += dgvStateChanges_UserDeletedRow; - // + // // columnStateName - // + // columnStateName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; columnStateName.DataPropertyName = "StateName"; columnStateName.HeaderText = "State name"; columnStateName.Name = "columnStateName"; columnStateName.ReadOnly = true; - // + // // columnStateId - // + // columnStateId.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; columnStateId.DataPropertyName = "StateId"; columnStateId.FillWeight = 50F; columnStateId.HeaderText = "State ID"; columnStateId.Name = "columnStateId"; - // + // // columnLowFrame - // + // columnLowFrame.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; columnLowFrame.DataPropertyName = "LowFrame"; columnLowFrame.FillWeight = 50F; columnLowFrame.HeaderText = "Low frame"; columnLowFrame.Name = "columnLowFrame"; - // + // // columnHighFrame - // + // columnHighFrame.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; columnHighFrame.DataPropertyName = "HighFrame"; columnHighFrame.FillWeight = 50F; columnHighFrame.HeaderText = "High frame"; columnHighFrame.Name = "columnHighFrame"; - // + // // columnNextAnimation - // + // columnNextAnimation.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; columnNextAnimation.DataPropertyName = "NextAnimation"; columnNextAnimation.FillWeight = 50F; columnNextAnimation.HeaderText = "Next anim"; columnNextAnimation.Name = "columnNextAnimation"; - // - // columnNextFrame - // - columnNextFrame.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; - columnNextFrame.DataPropertyName = "NextFrame"; - columnNextFrame.FillWeight = 50F; - columnNextFrame.HeaderText = "Next frame"; - columnNextFrame.Name = "columnNextFrame"; - // + // + // columnNextLowFrame + // + columnNextLowFrame.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + columnNextLowFrame.DataPropertyName = "NextLowFrame"; + columnNextLowFrame.FillWeight = 50F; + columnNextLowFrame.HeaderText = "Next low frame"; + columnNextLowFrame.Name = "columnNextLowFrame"; + // + // columnNextHighFrame + // + columnNextHighFrame.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + columnNextHighFrame.DataPropertyName = "NextHighFrame"; + columnNextHighFrame.FillWeight = 50F; + columnNextHighFrame.HeaderText = "Next high frame"; + columnNextHighFrame.Name = "columnNextHighFrame"; + // + // columnBlendFrames + // + columnBlendFrames.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + columnBlendFrames.DataPropertyName = "BlendFrames"; + columnBlendFrames.FillWeight = 50F; + columnBlendFrames.HeaderText = "Blend frames"; + columnBlendFrames.Name = "columnBlendFrames"; + // + // columnBlendCurve + // + columnBlendCurve.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + columnBlendCurve.FillWeight = 50F; + columnBlendCurve.HeaderText = "Blend curve"; + columnBlendCurve.Name = "columnBlendCurve"; + columnBlendCurve.Text = ""; + // // btCancel - // + // btCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; btCancel.Checked = false; btCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - btCancel.Location = new System.Drawing.Point(721, 336); + btCancel.Location = new System.Drawing.Point(761, 336); btCancel.Name = "btCancel"; btCancel.Size = new System.Drawing.Size(81, 23); btCancel.TabIndex = 50; btCancel.Text = "Cancel"; btCancel.Click += btCancel_Click; - // + // // btOk - // + // btOk.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; btOk.Checked = false; - btOk.Location = new System.Drawing.Point(634, 336); + btOk.Location = new System.Drawing.Point(674, 336); btOk.Name = "btOk"; btOk.Size = new System.Drawing.Size(81, 23); btOk.TabIndex = 51; btOk.Text = "OK"; btOk.Click += btOk_Click; - // + // // butPlayStateChange - // + // butPlayStateChange.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; butPlayStateChange.Checked = false; butPlayStateChange.Image = Properties.Resources.actions_play_16; - butPlayStateChange.Location = new System.Drawing.Point(502, 290); + butPlayStateChange.Location = new System.Drawing.Point(802, 290); butPlayStateChange.Name = "butPlayStateChange"; butPlayStateChange.Size = new System.Drawing.Size(28, 24); butPlayStateChange.TabIndex = 50; toolTip1.SetToolTip(butPlayStateChange, "Play state change in chain mode"); butPlayStateChange.Click += butPlayStateChange_Click; - // + // // dgvControls - // + // dgvControls.AlwaysInsertAtZero = false; dgvControls.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; dgvControls.Enabled = false; - dgvControls.Location = new System.Drawing.Point(502, 22); + dgvControls.Location = new System.Drawing.Point(802, 22); dgvControls.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); dgvControls.MinimumSize = new System.Drawing.Size(0, 32); dgvControls.Name = "dgvControls"; dgvControls.Size = new System.Drawing.Size(28, 258); dgvControls.TabIndex = 49; - // + // // lblStateChangeAnnouncement - // + // lblStateChangeAnnouncement.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; lblStateChangeAnnouncement.ForeColor = System.Drawing.Color.Gray; lblStateChangeAnnouncement.Location = new System.Drawing.Point(8, 341); @@ -175,134 +193,38 @@ private void InitializeComponent() lblStateChangeAnnouncement.TabIndex = 53; lblStateChangeAnnouncement.Text = "Pending state change..."; lblStateChangeAnnouncement.Visible = false; - // - // nudBlendEndFrame - // - nudBlendEndFrame.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; - nudBlendEndFrame.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - nudBlendEndFrame.Location = new System.Drawing.Point(176, 46); - nudBlendEndFrame.LoopValues = false; - nudBlendEndFrame.Maximum = new decimal(new int[] { 255, 0, 0, 0 }); - nudBlendEndFrame.Name = "nudBlendEndFrame"; - nudBlendEndFrame.Size = new System.Drawing.Size(71, 22); - nudBlendEndFrame.TabIndex = 103; - toolTip1.SetToolTip(nudBlendEndFrame, "Ending frame for blended transition in the next animation"); - nudBlendEndFrame.ValueChanged += nudBlendEndFrame_ValueChanged; - // - // nudBlendFrameCount - // - nudBlendFrameCount.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; - nudBlendFrameCount.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - nudBlendFrameCount.Location = new System.Drawing.Point(176, 19); - nudBlendFrameCount.LoopValues = false; - nudBlendFrameCount.Maximum = new decimal(new int[] { 255, 0, 0, 0 }); - nudBlendFrameCount.Name = "nudBlendFrameCount"; - nudBlendFrameCount.Size = new System.Drawing.Size(71, 22); - nudBlendFrameCount.TabIndex = 100; - toolTip1.SetToolTip(nudBlendFrameCount, "Blending duration to the next animation in frames"); - nudBlendFrameCount.ValueChanged += nudBlendFrameCount_ValueChanged; - // - // cbBlendPreset - // - cbBlendPreset.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - cbBlendPreset.FormattingEnabled = true; - cbBlendPreset.Items.AddRange(new object[] { "Linear", "Ease In", "Ease Out", "Ease In and Out" }); - cbBlendPreset.Location = new System.Drawing.Point(53, 291); - cbBlendPreset.Name = "cbBlendPreset"; - cbBlendPreset.Size = new System.Drawing.Size(194, 23); - cbBlendPreset.TabIndex = 105; - toolTip1.SetToolTip(cbBlendPreset, "Predefined curve preset"); - cbBlendPreset.SelectedIndexChanged += cbBlendPreset_SelectedIndexChanged; - // - // bezierCurveEditor - // - bezierCurveEditor.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - bezierCurveEditor.Location = new System.Drawing.Point(6, 74); - bezierCurveEditor.Margin = new System.Windows.Forms.Padding(5, 3, 5, 3); - bezierCurveEditor.Name = "bezierCurveEditor"; - bezierCurveEditor.Size = new System.Drawing.Size(241, 211); - bezierCurveEditor.TabIndex = 102; - toolTip1.SetToolTip(bezierCurveEditor, "Specify blending curve by dragging handles"); - bezierCurveEditor.ValueChanged += bezierCurveEditor_ValueChanged; - // + // // butApply - // + // butApply.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; butApply.Checked = false; - butApply.Location = new System.Drawing.Point(548, 336); + butApply.Location = new System.Drawing.Point(588, 336); butApply.Name = "butApply"; butApply.Size = new System.Drawing.Size(80, 23); butApply.TabIndex = 102; butApply.Text = "Apply"; butApply.Click += butApply_Click; - // + // // stateChangeGroup - // + // stateChangeGroup.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; stateChangeGroup.Controls.Add(dgvControls); stateChangeGroup.Controls.Add(butPlayStateChange); stateChangeGroup.Controls.Add(dgvStateChanges); stateChangeGroup.Location = new System.Drawing.Point(5, 9); stateChangeGroup.Name = "stateChangeGroup"; - stateChangeGroup.Size = new System.Drawing.Size(537, 321); + stateChangeGroup.Size = new System.Drawing.Size(837, 321); stateChangeGroup.TabIndex = 104; stateChangeGroup.TabStop = false; stateChangeGroup.Text = "State change editor"; - // - // blendingGroup - // - blendingGroup.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; - blendingGroup.Controls.Add(darkLabel1); - blendingGroup.Controls.Add(cbBlendPreset); - blendingGroup.Controls.Add(nudBlendEndFrame); - blendingGroup.Controls.Add(nudBlendFrameCount); - blendingGroup.Controls.Add(darkLabel3); - blendingGroup.Controls.Add(darkLabel2); - blendingGroup.Controls.Add(bezierCurveEditor); - blendingGroup.Location = new System.Drawing.Point(548, 9); - blendingGroup.Name = "blendingGroup"; - blendingGroup.Size = new System.Drawing.Size(254, 321); - blendingGroup.TabIndex = 105; - blendingGroup.TabStop = false; - blendingGroup.Text = "Animation blending"; - // - // darkLabel1 - // - darkLabel1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; - darkLabel1.AutoSize = true; - darkLabel1.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - darkLabel1.Location = new System.Drawing.Point(6, 295); - darkLabel1.Name = "darkLabel1"; - darkLabel1.Size = new System.Drawing.Size(41, 13); - darkLabel1.TabIndex = 106; - darkLabel1.Text = "Preset:"; - // - // darkLabel3 - // - darkLabel3.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - darkLabel3.Location = new System.Drawing.Point(6, 48); - darkLabel3.Name = "darkLabel3"; - darkLabel3.Size = new System.Drawing.Size(178, 13); - darkLabel3.TabIndex = 104; - darkLabel3.Text = "Next anim blending duration:"; - // - // darkLabel2 TODO: Should be in column instead. - // - darkLabel2.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - darkLabel2.Location = new System.Drawing.Point(6, 21); - darkLabel2.Name = "darkLabel2"; - darkLabel2.Size = new System.Drawing.Size(178, 13); - darkLabel2.TabIndex = 101; - darkLabel2.Text = "Next high frame:"; - // + // // FormStateChangesEditor - // + // AcceptButton = btOk; AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; CancelButton = btCancel; - ClientSize = new System.Drawing.Size(807, 364); - Controls.Add(blendingGroup); + ClientSize = new System.Drawing.Size(847, 364); Controls.Add(stateChangeGroup); Controls.Add(butApply); Controls.Add(lblStateChangeAnnouncement); @@ -317,11 +239,7 @@ private void InitializeComponent() StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; Text = "State changes"; ((System.ComponentModel.ISupportInitialize)dgvStateChanges).EndInit(); - ((System.ComponentModel.ISupportInitialize)nudBlendEndFrame).EndInit(); - ((System.ComponentModel.ISupportInitialize)nudBlendFrameCount).EndInit(); stateChangeGroup.ResumeLayout(false); - blendingGroup.ResumeLayout(false); - blendingGroup.PerformLayout(); ResumeLayout(false); } @@ -339,21 +257,11 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewTextBoxColumn columnLowFrame; private System.Windows.Forms.DataGridViewTextBoxColumn columnHighFrame; private System.Windows.Forms.DataGridViewTextBoxColumn columnNextAnimation; - private System.Windows.Forms.DataGridViewTextBoxColumn columnNextFrame; + private System.Windows.Forms.DataGridViewTextBoxColumn columnNextLowFrame; + private System.Windows.Forms.DataGridViewTextBoxColumn columnNextHighFrame; + private System.Windows.Forms.DataGridViewTextBoxColumn columnBlendFrames; + private DarkUI.Controls.DarkDataGridViewButtonColumn columnBlendCurve; private DarkUI.Controls.DarkButton butApply; private DarkUI.Controls.DarkGroupBox stateChangeGroup; - private DarkUI.Controls.DarkGroupBox blendingGroup; - private DarkUI.Controls.DarkCheckBox cbRotR; - private DarkUI.Controls.DarkCheckBox cbPosX; - private DarkUI.Controls.DarkCheckBox cbRotP; - private DarkUI.Controls.DarkCheckBox cbPosY; - private DarkUI.Controls.DarkCheckBox cbRotY; - private Controls.BezierCurveEditor bezierCurveEditor; - private DarkUI.Controls.DarkNumericUpDown nudBlendFrameCount; - private DarkUI.Controls.DarkLabel darkLabel2; - private DarkUI.Controls.DarkNumericUpDown nudBlendEndFrame; - private DarkUI.Controls.DarkLabel darkLabel3; - private DarkUI.Controls.DarkLabel darkLabel1; - private DarkUI.Controls.DarkComboBox cbBlendPreset; } -} \ No newline at end of file +} diff --git a/WadTool/Forms/FormStateChangesEditor.cs b/WadTool/Forms/FormStateChangesEditor.cs index 5a75d7ec32..65f9748878 100644 --- a/WadTool/Forms/FormStateChangesEditor.cs +++ b/WadTool/Forms/FormStateChangesEditor.cs @@ -1,4 +1,4 @@ -using DarkUI.Config; +using DarkUI.Config; using DarkUI.Extensions; using System; using System.Collections.Generic; @@ -6,9 +6,11 @@ using System.Linq; using System.Windows.Forms; using TombLib.Graphics; +using TombLib.LevelData; using TombLib.Types; using TombLib.Wad; using TombLib.Wad.Catalog; +using WadTool.Controls; namespace WadTool { @@ -28,22 +30,22 @@ private class WadStateChangeRow public ushort LowFrame { get; set; } public ushort HighFrame { get; set; } public ushort NextAnimation { get; set; } - public ushort NextFrame { get; set; } - public ushort BlendFrameCount { get; set; } - public ushort BlendEndFrame { get; set; } + public ushort NextLowFrame { get; set; } + public ushort NextHighFrame { get; set; } + public ushort BlendFrames { get; set; } public BezierCurve2 BlendCurve { get; set; } = BezierCurve2.Linear.Clone(); public WadStateChangeRow(string stateName, ushort stateId, ushort lowFrame, ushort highFrame, ushort nextAnimation, - ushort nextFrameLow, ushort nextFrameHigh, ushort blendFrameCount, BezierCurve2 blendCurve) + ushort nextLowFrame, ushort nextHighFrame, ushort blendFrames, BezierCurve2 blendCurve) { StateName = stateName; StateId = stateId; LowFrame = lowFrame; HighFrame = highFrame; NextAnimation = nextAnimation; - NextFrame = nextFrameLow; - BlendEndFrame = nextFrameHigh; - BlendFrameCount = blendFrameCount; + NextLowFrame = nextLowFrame; + NextHighFrame = nextHighFrame; + BlendFrames = blendFrames; BlendCurve = blendCurve.Clone(); } @@ -62,12 +64,10 @@ public FormStateChangesEditor(AnimationEditor editor, AnimationNode animation, W dgvControls.DataGridView = dgvStateChanges; dgvControls.Enabled = true; - // TODO: Enable when animation blending is finished. - if (true) // if (editor.Wad.GameVersion != TombLib.LevelData.TRVersion.Game.TombEngine) - { - blendingGroup.Visible = false; - stateChangeGroup.Width = blendingGroup.Left + blendingGroup.Width - stateChangeGroup.Left; - } + bool isTEN = _editor.Tool.DestinationWad.GameVersion == TRVersion.Game.TombEngine; + columnNextHighFrame.Visible = isTEN; + columnBlendFrames.Visible = isTEN; + columnBlendCurve.Visible = isTEN; Initialize(animation, newStateChange); _editor.Tool.EditorEventRaised += Tool_EditorEventRaised; @@ -94,7 +94,7 @@ private void Initialize(AnimationNode animation, WadStateChange newStateChange) foreach (var sc in _animation.WadAnimation.StateChanges) foreach (var d in sc.Dispatches) rows.Add(new WadStateChangeRow(TrCatalog.GetStateName(_editor.Tool.DestinationWad.GameVersion, _editor.Moveable.Id.TypeId, sc.StateId), - sc.StateId, d.InFrame, d.OutFrame, d.NextAnimation, d.NextFrameLow, d.NextFrameHigh, d.BlendFrameCount, d.BlendCurve)); + sc.StateId, d.InFrame, d.OutFrame, d.NextAnimation, d.NextLowFrame, d.NextHighFrame, d.BlendFrames, d.BlendCurve)); if (newStateChange != null && newStateChange.Dispatches.Count == 1) { @@ -103,9 +103,9 @@ private void Initialize(AnimationNode animation, WadStateChange newStateChange) newStateChange.Dispatches[0].InFrame, newStateChange.Dispatches[0].OutFrame, newStateChange.Dispatches[0].NextAnimation, - newStateChange.Dispatches[0].NextFrameLow, - newStateChange.Dispatches[0].NextFrameHigh, - newStateChange.Dispatches[0].BlendFrameCount, + newStateChange.Dispatches[0].NextLowFrame, + newStateChange.Dispatches[0].NextHighFrame, + newStateChange.Dispatches[0].BlendFrames, newStateChange.Dispatches[0].BlendCurve)); _createdNew = true; } @@ -167,7 +167,7 @@ private void ChangeState() if (dgvStateChanges.SelectedRows.Count > 0) { var item = ((IEnumerable)dgvStateChanges.DataSource).ElementAt(dgvStateChanges.SelectedRows[0].Index); - _editor.Tool.ChangeState(item.NextAnimation, item.NextFrame, item.LowFrame, item.HighFrame); + _editor.Tool.ChangeState(item.NextAnimation, item.NextLowFrame, item.LowFrame, item.HighFrame, item.BlendFrames, item.BlendCurve); lblStateChangeAnnouncement.Text = "Pending state change to anim #" + item.NextAnimation; } @@ -188,9 +188,9 @@ private void SaveChanges() var sc = tempDictionary[row.StateId]; sc.StateId = row.StateId; - var newDispatch = new WadAnimDispatch(row.LowFrame, row.HighFrame, row.NextAnimation, row.NextFrame); - newDispatch.NextFrameHigh = row.BlendEndFrame; - newDispatch.BlendFrameCount = row.BlendFrameCount; + var newDispatch = new WadAnimDispatch(row.LowFrame, row.HighFrame, row.NextAnimation, row.NextLowFrame); + newDispatch.NextHighFrame = row.NextHighFrame; + newDispatch.BlendFrames = row.BlendFrames; newDispatch.BlendCurve = row.BlendCurve; sc.Dispatches.Add(newDispatch); @@ -223,6 +223,21 @@ private void DiscardChanges() _initializing = false; } + private void ShowBlendCurveEditor(int rowIndex) + { + var item = ((IEnumerable)dgvStateChanges.DataSource).ElementAt(rowIndex); + + using (var form = new FormBlendCurveEditor(item.BlendCurve)) + { + if (form.ShowDialog(this) == DialogResult.OK) + { + item.BlendCurve = form.ResultCurve; + dgvStateChanges.InvalidateRow(rowIndex); + SaveChanges(); + } + } + } + private void dgvStateChanges_CellFormattingSafe(object sender, DarkUI.Controls.DarkDataGridViewSafeCellFormattingEventArgs e) { if (!(e.Row.DataBoundItem is WadStateChangeRow)) @@ -243,16 +258,55 @@ private void dgvStateChanges_CellFormattingSafe(object sender, DarkUI.Controls.D } } - private void dgvStateChanges_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) => ChangeState(); + private void dgvStateChanges_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) + { + if (e.RowIndex < 0 || e.ColumnIndex < 0) + return; + + if (dgvStateChanges.Columns[e.ColumnIndex] != columnBlendCurve) + return; + + if (!(dgvStateChanges.Rows[e.RowIndex].DataBoundItem is WadStateChangeRow item)) + return; + + // Paint all standard parts (background, borders, selection outline) then draw custom curve preview. + e.Paint(e.CellBounds, DataGridViewPaintParts.All); + BezierCurveEditor.DrawPreview(e.Graphics, e.CellBounds, item.BlendCurve); + e.Handled = true; + } + + private void dgvStateChanges_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0) + return; + + if (dgvStateChanges.Columns[e.ColumnIndex] == columnBlendCurve) + ShowBlendCurveEditor(e.RowIndex); + } + + private void dgvStateChanges_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) + { + if (e.RowIndex < 0) + return; + + if (dgvStateChanges.Columns[e.ColumnIndex] == columnBlendCurve) + return; + + ChangeState(); + } private void dgvStateChanges_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { if (e.ColumnIndex == 0) return; + var column = dgvStateChanges.Columns[e.ColumnIndex]; + if (column == columnBlendCurve) + return; + try { var cell = dgvStateChanges.Rows[e.RowIndex].Cells[e.ColumnIndex]; - var name = dgvStateChanges.Columns[e.ColumnIndex].Name; + var name = column.Name; // For some reason, validating against UInt16 type results in unrecoverable DGV exception on // wrong incoming values, so we're validating against Int16 and filtering out negative values afterwards. @@ -272,12 +326,6 @@ private void dgvStateChanges_CellValidating(object sender, DataGridViewCellValid { limit = (Int16)(_editor.Animations.Count - 1); } - else if (name == columnNextFrame.Name) - { - Int16 limitNew = 0; - if (Int16.TryParse(dgvStateChanges.Rows[e.RowIndex].Cells[4].Value.ToString(), out limitNew)) - limit = (Int16)(_editor.GetRealNumberOfFrames(limitNew)); - } else if (name == columnLowFrame.Name) { Int16 limitNew = 0; @@ -288,6 +336,21 @@ private void dgvStateChanges_CellValidating(object sender, DataGridViewCellValid { limit = (Int16)(_editor.GetRealNumberOfFrames()); } + else if (name == columnNextLowFrame.Name) + { + Int16 limitNew = 0; + Int16 nextAnim = 0; + Int16.TryParse(dgvStateChanges.Rows[e.RowIndex].Cells[4].Value.ToString(), out nextAnim); + + if (Int16.TryParse(dgvStateChanges.Rows[e.RowIndex].Cells[6].Value.ToString(), out limitNew)) + limit = limitNew == 0 ? (Int16)(_editor.GetRealNumberOfFrames(nextAnim)) : limitNew; + } + else if (name == columnNextHighFrame.Name) + { + Int16 nextAnim = 0; + Int16.TryParse(dgvStateChanges.Rows[e.RowIndex].Cells[4].Value.ToString(), out nextAnim); + limit = (Int16)(_editor.GetRealNumberOfFrames(nextAnim)); + } } if (parsedValue > limit) @@ -322,74 +385,11 @@ private void butApply_Click(object sender, EventArgs e) } private void butPlayStateChange_Click(object sender, EventArgs e) => ChangeState(); - private void dgvStateChanges_CellEndEdit(object sender, System.Windows.Forms.DataGridViewCellEventArgs e) => SaveChanges(); - private void dgvStateChanges_UserDeletedRow(object sender, System.Windows.Forms.DataGridViewRowEventArgs e) => SaveChanges(); + private void dgvStateChanges_CellEndEdit(object sender, DataGridViewCellEventArgs e) => SaveChanges(); + private void dgvStateChanges_UserDeletedRow(object sender, DataGridViewRowEventArgs e) => SaveChanges(); private void dgvStateChanges_SelectionChanged(object sender, EventArgs e) { - bool selection = (dgvStateChanges.SelectedRows.Count > 0) && _editor.Wad.GameVersion == TombLib.LevelData.TRVersion.Game.TombEngine; - - nudBlendEndFrame.Enabled = selection; - nudBlendFrameCount.Enabled = selection; - bezierCurveEditor.Enabled = selection; - cbBlendPreset.Enabled = selection; - - if (!selection) - return; - - var item = ((IEnumerable)dgvStateChanges.DataSource).ElementAt(dgvStateChanges.SelectedRows[0].Index); - - nudBlendFrameCount.Value = (decimal)item.BlendFrameCount; - nudBlendEndFrame.Value = (decimal)item.BlendEndFrame; - bezierCurveEditor.Value = item.BlendCurve; - cbBlendPreset.SelectedIndex = -1; - } - - private void nudBlendFrameCount_ValueChanged(object sender, EventArgs e) - { - if (dgvStateChanges.SelectedRows.Count <= 0) - return; - - var item = ((IEnumerable)dgvStateChanges.DataSource).ElementAt(dgvStateChanges.SelectedRows[0].Index); - item.BlendFrameCount = (ushort)nudBlendFrameCount.Value; - } - - private void nudBlendEndFrame_ValueChanged(object sender, EventArgs e) - { - if (dgvStateChanges.SelectedRows.Count <= 0) - return; - - var item = ((IEnumerable)dgvStateChanges.DataSource).ElementAt(dgvStateChanges.SelectedRows[0].Index); - item.BlendEndFrame = (ushort)nudBlendEndFrame.Value; - } - - private void cbBlendPreset_SelectedIndexChanged(object sender, EventArgs e) - { - switch (cbBlendPreset.SelectedIndex) - { - case 0: - bezierCurveEditor.Value.Set(BezierCurve2.Linear); - break; - - case 1: - bezierCurveEditor.Value.Set(BezierCurve2.EaseIn); - break; - - case 2: - bezierCurveEditor.Value.Set(BezierCurve2.EaseOut); - break; - - case 3: - bezierCurveEditor.Value.Set(BezierCurve2.EaseInOut); - break; - } - - bezierCurveEditor.UpdateUI(); - } - - private void bezierCurveEditor_ValueChanged(object sender, EventArgs e) - { - cbBlendPreset.SelectedIndex = -1; } } } diff --git a/WadTool/Forms/FormStateChangesEditor.resx b/WadTool/Forms/FormStateChangesEditor.resx index 0bb5f1f027..037d9424ed 100644 --- a/WadTool/Forms/FormStateChangesEditor.resx +++ b/WadTool/Forms/FormStateChangesEditor.resx @@ -135,27 +135,6 @@ True - - True - - - True - - - True - - - True - - - True - - - True - - - 17, 17 - 17, 17 diff --git a/WadTool/WadActions.cs b/WadTool/WadActions.cs index afedd1a567..a9515bd6fa 100644 --- a/WadTool/WadActions.cs +++ b/WadTool/WadActions.cs @@ -1326,7 +1326,7 @@ public static WadAnimation ImportAnimationFromTrw(string fileName, int sourceAni nextAnimation = reader.ReadInt16(); disp.NextAnimation = (ushort)(MathC.Clamp(sourceAnimIndex + nextAnimation, 0, ushort.MaxValue)); - disp.NextFrameLow = reader.ReadUInt16(); + disp.NextLowFrame = reader.ReadUInt16(); sc.Dispatches.Add(disp); padCounter += 4; // 4 bytes per 1 dispatch, don't ask why. diff --git a/WadTool/WadToolClass.cs b/WadTool/WadToolClass.cs index e9445f3345..2236a04cbf 100644 --- a/WadTool/WadToolClass.cs +++ b/WadTool/WadToolClass.cs @@ -4,6 +4,7 @@ using TombLib.Forms; using TombLib.Graphics; using TombLib.LevelData; +using TombLib.Types; using TombLib.Wad; namespace WadTool @@ -281,10 +282,19 @@ public class AnimationEditorStateChangeEvent : IEditorEvent public int NextAnimation { get; internal set; } public int NextFrame { get; internal set; } public VectorInt2 FrameRange { get; internal set; } + public int BlendFrames { get; internal set; } + public BezierCurve2 BlendCurve { get; internal set; } } - public void ChangeState(int nextAnim, int nextFrame, int frameLow, int frameHigh) + public void ChangeState(int nextAnim, int nextFrame, int frameLow, int frameHigh, int blendFrames = 0, BezierCurve2 blendCurve = null) { - RaiseEvent(new AnimationEditorStateChangeEvent { NextAnimation = nextAnim, NextFrame = nextFrame, FrameRange = new VectorInt2(frameLow, frameHigh) } ); + RaiseEvent(new AnimationEditorStateChangeEvent + { + NextAnimation = nextAnim, + NextFrame = nextFrame, + FrameRange = new VectorInt2(frameLow, frameHigh), + BlendFrames = blendFrames, + BlendCurve = blendCurve + }); } // Animation editor playback toggle