using Microsoft.Xna.Framework; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; #pragma warning disable 169 #pragma warning disable 649 namespace OpenVIII.Battle { public partial class Camera { #region Structs [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 1092)] [SuppressMessage("ReSharper", "UnassignedReadonlyField")] [SuppressMessage("ReSharper", "UnusedMember.Global")] public struct CameraStruct { public byte AnimationId { get; set; } //000 private byte KeyframeCount; public ushort ControlWord { get; private set; } public ushort StartingFov { get; private set; } //usually ~280 public ushort EndingFov { get; private set; } //006 private ushort StartingCameraRoll; //usually 0 unless you're aiming for some wicked animation private ushort EndingCameraRoll; // private readonly ushort _startingTime; //usually 0, that's pretty logical /// /// Time is calculated from number of frames. You basically set starting position /// World+lookat and ending position, then mark number of frames to interpolate between /// them. Every frame is one draw call and it costs 16. /// /// ReSharper disable once CommentTypo /// starting time needs to be equal or higher for next animation frame to be read; If next frame==0xFFFF then it's all done public ushort Time { get; private set; } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] private readonly byte[] _unkBytes; //010 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly ushort[] StartFramesOffsets; //024 - start frames for each key frame? [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly short[] _cameraWorldZ; //064 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly short[] _cameraWorldX; //0A4 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly short[] _cameraWorldY; //0E4 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly byte[] IsFrameDurationsShot; //124 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly short[] _cameraLookAtZ; //144 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly short[] _cameraLookAtX; //184 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly short[] _cameraLookAtY; //1C4 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] private readonly byte[] IsFrameEndingShots; //204 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] private readonly byte[] _unkByte224; //224 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] private readonly byte[] _unkByte2A4; //2A4 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] private readonly byte[] _unkByte324; //324 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] private readonly byte[] _unkByte3A4; //3A4 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] private readonly byte[] _unkByte424; //424 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] private readonly byte[] _unkByte4A4; //4A4 private static Vector3 Offset => new Vector3(40, 40, -40); //private (Vector3 World, Vector3 LookAt) this[int i] => (CameraWorld(i), CameraLookAt(i)); private Vector3 CameraWorld(int i) => new Vector3( _cameraWorldX[i], -(_cameraWorldY[i]), -(_cameraWorldZ[i])) / Memory.CameraScale + Offset; private Vector3 CameraLookAt(int i) => new Vector3( _cameraLookAtX[i], -(_cameraLookAtY[i]), -(_cameraLookAtZ[i])) / Memory.CameraScale + Offset; public void UpdateTime() => CurrentTime += Memory.ElapsedGameTime; public TimeSpan CurrentTime { get; private set; } public TimeSpan TotalTime => TimeSpan.FromTicks(TotalTimePerFrame.Ticks * Time); /// /// (1000) milliseconds / frames per second, maybe not quite fps. /// private static TimeSpan TotalTimePerFrame => TimeSpan.FromMilliseconds(1000d / 240d); public void ResetTime() => CurrentTime = TimeSpan.Zero; public void ReadAnimation(BinaryReader br) { ControlWord = br.ReadUInt16(); if (ControlWord == 0xFFFF) return; var currentPosition = br.ReadUInt16(); //getter for *current_position br.BaseStream.Seek(-2, SeekOrigin.Current); //roll back one WORD because no increment switch ((ControlWord >> 6) & 3) { case 1: StartingFov = 0x200; EndingFov = 0x200; break; case 2: StartingFov = currentPosition; EndingFov = currentPosition; br.ReadUInt16(); //current_position++ break; case 3: StartingFov = currentPosition; br.BaseStream.Seek(2, SeekOrigin .Current); //skipping WORD, because we already rolled back one WORD above this switch currentPosition = br.ReadUInt16(); EndingFov = currentPosition; break; } switch ((ControlWord >> 8) & 3) { case 0: //TODO!! StartingCameraRoll = 00000000; //TODO, what's ff8vars.unkWord1D977A2? EndingCameraRoll = 00000000; //same as above; cam->unkWord00A = ff8vars.unkWord1D977A2; break; case 1: StartingCameraRoll = 0; EndingCameraRoll = 0; break; case 2: currentPosition = br.ReadUInt16(); //* + current_position++; StartingCameraRoll = currentPosition; EndingCameraRoll = currentPosition; break; case 3: currentPosition = br.ReadUInt16(); //* + current_position++; StartingCameraRoll = currentPosition; currentPosition = br.ReadUInt16(); //* + current_position++; EndingCameraRoll = currentPosition; break; } byte keyFrameCount = 0; ushort totalFrameCount = 0; switch (ControlWord & 1) { case 0: while (true ) //I'm setting this to true and breaking in code as this works on peeking on next variable via pointer and that's not possible here without unsafe block { StartFramesOffsets[keyFrameCount] = totalFrameCount; //looks like this is the camera index currentPosition = br.ReadUInt16(); if ((short)currentPosition < 0 ) //reverse of *current_position >= 0, also cast to signed is important here break; totalFrameCount += (ushort)(currentPosition * 16); //here is increment of short*, but I already did that above IsFrameDurationsShot[keyFrameCount] = (byte)(br .ReadUInt16() ); //cam->unkByte124[keyFrameCount] = *current_position++; - looks like we are wasting one byte due to integer sizes _cameraWorldX[keyFrameCount] = br.ReadInt16(); _cameraWorldY[keyFrameCount] = br.ReadInt16(); _cameraWorldZ[keyFrameCount] = br.ReadInt16(); IsFrameEndingShots[keyFrameCount] = (byte)(br.ReadUInt16()); //m->unkByte204[keyFrameCount] = *current_position++; _cameraLookAtX[keyFrameCount] = br.ReadInt16(); _cameraLookAtY[keyFrameCount] = br.ReadInt16(); _cameraLookAtZ[keyFrameCount] = br.ReadInt16(); keyFrameCount++; } if (keyFrameCount > 2) { //ff8Functions.Sub50D010(cam->unkWord024, cam->unkWord064, cam->unkWord0A4, cam->unkWord0E4, keyFrameCount, cam->unkByte224, cam->unkByte2A4, cam->unkByte324); //ff8Functions.Sub50D010(cam->unkWord024, cam->unkWord144, cam->unkWord184, cam->unkWord1C4, keyFrameCount, cam->unkByte3A4, cam->unkByte424, cam->unkByte4A4); } break; case 1: { goto case 0; //if (currentPosition >= 0) //{ // var local14 = (short)(br.BaseStream.Position + 5 * 2); // var local10 = (short)(br.BaseStream.Position + 6 * 2); // var local2C = (short)(br.BaseStream.Position + 7 * 2); // var local18 = (short)(br.BaseStream.Position + 1 * 2); // var local1C = (short)(br.BaseStream.Position + 2 * 2); // while (true) // { // StartFramesOffsets[keyFrameCount] = totalFrameCount; // currentPosition = br.ReadUInt16(); // if ((short)currentPosition < 0) //reverse of *current_position >= 0, also cast to signed is important here // break; // totalFrameCount += (ushort)(currentPosition * 16); // //ff8Functions.Sub503AE0(++local18, ++local1C, ++ebx, *(BYTE*)current_position, &cam->unkWord064[keyFrameCount], &cam->unkWord0A4[keyFrameCount], &cam->unkWord0E4[keyFrameCount]); // //ff8Functions.Sub503AE0(++local14, ++local10, ++local2C, *(BYTE*)(current_position + 4), &cam->unkWord144[keyFrameCount], &cam->unkWord184[keyFrameCount], &cam->unkWord1C4[keyFrameCount]); // IsFrameEndingShots[keyFrameCount] = 0xFB; // IsFrameDurationsShot[keyFrameCount] = 0xFB; // local1C += 8; // local18 += 8; // currentPosition += 8; // local2C += 8; // //ebx += 8; // local10 += 8; // local14 += 8; // keyFrameCount++; // } //} //break; } } if ((ControlWord & 0x3E) == 0x1E) { //ff8Functions.Sub503300(); } KeyframeCount = keyFrameCount; Time = totalFrameCount; //cam.startingTime = 0; CurrentTime = TimeSpan.Zero; } public bool Done => CurrentTime >= TotalTime || (_cameraLookAtX[0], _cameraLookAtY[0], _cameraLookAtZ[0], _cameraWorldX[0], _cameraWorldY[0], _cameraWorldZ[0]) == (0, 0, 0, 0, 0, 0); public (Vector3 CamTarget, Vector3 CamPosition, Matrix View, Matrix Projection) UpdatePosition() { var step = CurrentTime.Ticks / (float)TotalTime.Ticks; if (step > 1f) step = 1f; var camTarget = Vector3.SmoothStep(CameraLookAt(0), CameraLookAt(1), step); var camPosition = Vector3.SmoothStep(CameraWorld(0), CameraWorld(1), step); var fov = MathHelper.ToRadians( (float)(2f * Math.Atan(240f / (2f * MathHelper.SmoothStep(StartingFov, EndingFov, step))) * 57.29577951f)); var viewMatrix = Matrix.CreateLookAt(camPosition, camTarget, Vector3.Up); var projectionMatrix = (Memory.Graphics != null) ? Matrix.CreatePerspectiveFieldOfView(fov, Memory.Graphics.GraphicsDevice.Viewport.AspectRatio, 1f, 1000f) : Matrix.Identity; return (camTarget, camPosition, viewMatrix, projectionMatrix); } } #endregion Structs } }