using System; using System.Collections.Generic; using System.Linq; using Vintagestory.API.MathTools; using VSClientOptimizer.Config; using VSClientOptimizer.Math; using VSClientOptimizer.Interpolation; namespace VSClientOptimizer.Performance { /// /// Frame Generation - Creates intermediate frames between real frames for smoother motion /// Similar to AMD FSR Frame Generation or NVIDIA DLSS 3 Frame Generation /// public class FrameGenerator { private Queue frameHistory = new Queue(); private const int MaxFrameHistory = 3; private float generationMultiplier = 2.0f; private bool useMotionVectors = true; public class FrameSnapshot { public long TimestampMs; public Dictionary EntityStates; public FrameSnapshot(long timestamp) { TimestampMs = timestamp; EntityStates = new Dictionary(); } } public FrameGenerator(float multiplier, bool useMotion) { generationMultiplier = System.Math.Max(1.0f, System.Math.Min(4.0f, multiplier)); // Clamp 1-4x useMotionVectors = useMotion; } public void AddFrame(long timestamp, Dictionary states) { var snapshot = new FrameSnapshot(timestamp); foreach (var kvp in states) { snapshot.EntityStates[kvp.Key] = kvp.Value; } frameHistory.Enqueue(snapshot); if (frameHistory.Count > MaxFrameHistory) frameHistory.Dequeue(); } public List GenerateIntermediateFrames() { var generatedFrames = new List(); if (frameHistory.Count < 2 || generationMultiplier <= 1.0f) return generatedFrames; var frames = frameHistory.ToArray(); var lastFrame = frames[frames.Length - 2]; var currentFrame = frames[frames.Length - 1]; long timeDiff = currentFrame.TimestampMs - lastFrame.TimestampMs; if (timeDiff <= 0) return generatedFrames; // Generate intermediate frames int intermediateCount = (int)(generationMultiplier - 1); for (int i = 1; i <= intermediateCount; i++) { float t = i / generationMultiplier; long interpTime = lastFrame.TimestampMs + (long)(timeDiff * t); var interpFrame = new FrameSnapshot(interpTime); // Interpolate each entity's state foreach (var entityId in lastFrame.EntityStates.Keys) { if (currentFrame.EntityStates.TryGetValue(entityId, out var currentState) && lastFrame.EntityStates.TryGetValue(entityId, out var lastState)) { var interpState = InterpolateEntityState(lastState, currentState, t); interpFrame.EntityStates[entityId] = interpState; } } generatedFrames.Add(interpFrame); } return generatedFrames; } private EntityPositionSnapshot InterpolateEntityState( EntityPositionSnapshot from, EntityPositionSnapshot to, float t) { // Position interpolation Vector3 fromPos = new Vector3(from.Position.X, from.Position.Y, from.Position.Z); Vector3 toPos = new Vector3(to.Position.X, to.Position.Y, to.Position.Z); Vector3 interpPos; if (useMotionVectors && (from.Velocity.X != 0 || from.Velocity.Y != 0 || from.Velocity.Z != 0)) { // Use motion vectors for smoother interpolation double deltaTime = (to.TimestampMs - from.TimestampMs) / 1000.0; Vector3 motionPredicted = fromPos + (from.Velocity * (deltaTime * t)); interpPos = Vector3.Lerp(Vector3.Lerp(fromPos, toPos, t), motionPredicted, 0.3f); } else { interpPos = Vector3.Lerp(fromPos, toPos, t); } // Rotation interpolation Rotation3 fromRot = new Rotation3(from.Rotation.X, from.Rotation.Y, from.Rotation.Z); Rotation3 toRot = new Rotation3(to.Rotation.X, to.Rotation.Y, to.Rotation.Z); Rotation3 interpRot = Rotation3.Lerp(fromRot, toRot, t); // Velocity interpolation Vector3 interpVel = Vector3.Lerp(from.Velocity, to.Velocity, t); long interpTime = from.TimestampMs + (long)((to.TimestampMs - from.TimestampMs) * t); return new EntityPositionSnapshot( new Vec3d(interpPos.X, interpPos.Y, interpPos.Z), new Vec3f(interpRot.Pitch, interpRot.Yaw, interpRot.Roll), interpTime, interpVel ); } } }