From 21ff2533370dbafaec83906719f5d67217300644 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sun, 4 Sep 2022 15:23:19 +0800 Subject: [PATCH] add spirit trail rec reader --- src/BallanceStalker.csproj | 2 + src/scripts/cores/managers/RecManager.cs | 21 ++- src/scripts/cores/rec/SpiritTrailRec.cs | 188 +++++++++++++++++++++-- src/scripts/cores/utils/BallStates.cs | 134 ++++++++++++++-- src/scripts/cores/utils/IRec.cs | 11 +- 5 files changed, 331 insertions(+), 25 deletions(-) diff --git a/src/BallanceStalker.csproj b/src/BallanceStalker.csproj index 8bb664b..4df8686 100644 --- a/src/BallanceStalker.csproj +++ b/src/BallanceStalker.csproj @@ -3,6 +3,8 @@ net472 + + \ No newline at end of file diff --git a/src/scripts/cores/managers/RecManager.cs b/src/scripts/cores/managers/RecManager.cs index b55ba48..d973540 100644 --- a/src/scripts/cores/managers/RecManager.cs +++ b/src/scripts/cores/managers/RecManager.cs @@ -1,4 +1,5 @@ -using System; +using BallanceStalker.Cores.Utils; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,5 +7,23 @@ using System.Threading.Tasks; namespace BallanceStalker.Cores.Managers { public class RecManager { + + public event Action RecAdded; + public event Action RecRemoved; + + private Dictionary mRecDict = new Dictionary(); + + public Guid AddSpiritTrailRec(string rec_folder) { + + } + + public void RemoveRec(Guid uuid) { + + } + + public void Tick(float delta) { + + } + } } diff --git a/src/scripts/cores/rec/SpiritTrailRec.cs b/src/scripts/cores/rec/SpiritTrailRec.cs index c8026a8..34d3d07 100644 --- a/src/scripts/cores/rec/SpiritTrailRec.cs +++ b/src/scripts/cores/rec/SpiritTrailRec.cs @@ -5,51 +5,217 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; +using System.IO; +using System.IO.Compression; +using zlib; +using System.Runtime.InteropServices; +using System.Numerics; namespace BallanceStalker.Cores.Rec { + + public struct SpiritTrailTrafo { + public int Frame; + public VxBallType TrafoType; + } + + public enum SpiritTrailRecType { + HS, + SR + } + public class SpiritTrailRec : Utils.IRec { - public SpiritTrailRec(string rec_folder) { + + static readonly float REC_DELTA = 1f / 8f; + + public SpiritTrailRec(string rec_folder, SpiritTrailRecType t) { mIsReady = mIsPlaying = false; - Task.Run(() => { - // load data + mFileFolder = rec_folder; + mFileType = t; + mFileCursor = 1; - lock (mStatusLock) { - mIsReady = true; - } - }); + mRecStateCursor = mRecTrafoCursor = 0; + mRecRemainDelta = 0f; } public event Action RecRegisterBall; - public event Action RecPlaying; - public event Action RecPaused; public event Action RecUnregisterBall; + public event Action RecNewBallState; + public event Action> RecNewBallStates; object mStatusLock; bool mIsReady, mIsPlaying; + string mFileFolder; + SpiritTrailRecType mFileType; + int mFileCursor; + + int mRecHSScore; + float mRecSRScore; + VxBallState[] mRecStates; + SpiritTrailTrafo[] mRecTrafos; + + int mRecStateCursor, mRecTrafoCursor; + float mRecRemainDelta; + + private void LoadData() { + // load data + bool success = true; + try { + // check exist + var filename = System.IO.Path.Combine(mFileFolder, mFileType == SpiritTrailRecType.HS ? $"hs{mFileCursor}.rec" : $"sr{mFileCursor}.rec"); + if (!System.IO.File.Exists(filename)) + throw new ArgumentOutOfRangeException("spirit trail index overflow."); + + // analyse file + using (var ms = new MemoryStream()) { + // decompress + int decomp; + using (var zo = new zlib.ZOutputStream(ms, 9)) { + using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { + // read header + var data = new byte[4]; + fs.Read(data, 0, 4); + decomp = BitConverter.ToInt32(data, 0); + + fs.CopyTo(zo); + } + zo.finish(); + } + + // check size + if (ms.Length != decomp) + throw new ArgumentOutOfRangeException("expected stream size if not matched real size in zlib decompression."); + + // read data + using (var br = new BinaryReader(ms, Encoding.ASCII, true)) { + // read basic data + mRecHSScore = br.ReadInt32(); + mRecSRScore = br.ReadSingle(); + int states_count = br.ReadInt32(); + int trafo_count = br.ReadInt32(); + + // read states data + mRecStates = new VxBallState[states_count]; + byte[] buffer = br.ReadBytes(states_count * 4 * (3 + 4)); + Buffer.BlockCopy(buffer, 0, mRecStates, 0, states_count); + //for (int i = 0; i < states_count; ++i) { + // mRecStates[i].Pos.X = br.ReadSingle(); + // mRecStates[i].Pos.Y = br.ReadSingle(); + // mRecStates[i].Pos.Z = br.ReadSingle(); + // mRecStates[i].Quat.X = br.ReadSingle(); + // mRecStates[i].Quat.Y = br.ReadSingle(); + // mRecStates[i].Quat.Z = br.ReadSingle(); + // mRecStates[i].Quat.W = br.ReadSingle(); + //} + + // read trafo data + mRecTrafos = new SpiritTrailTrafo[trafo_count]; + buffer = br.ReadBytes(states_count * 8); + Buffer.BlockCopy(buffer, 0, mRecTrafos, 0, trafo_count); + //for (int i = 0; i < trafo_count; ++i) { + // mRecTrafo[i].Frame = br.ReadInt32(); + // mRecTrafo[i].TrafoType = (BallType)br.ReadInt32(); + //} + } + } + + // increase sector + mFileCursor++; + } catch { + success = false; + } + + // feedback status + lock (mStatusLock) { + mIsReady = success; + } + } + + + public void Startup() { + RecRegisterBall?.Invoke(0, Path.GetFileName(mFileFolder)); + + Task.Run(() => { + LoadData(); + }); + } + + public void Shutdown() { + RecUnregisterBall?.Invoke(0); + } + public void Pause() { lock (mStatusLock) { mIsPlaying = false; } + //RecPaused?.Invoke(); } public void Play() { lock (mStatusLock) { mIsPlaying = true; } + //RecPlaying?.Invoke(); } public void Seek(float sec) { ; } - public void Tick(float delta_sec, List report) { + public void Tick(float delta_sec) { lock (mStatusLock) { if (!mIsPlaying || !mIsReady) return; } - + // increase delta + mRecRemainDelta += delta_sec; + if (mRecRemainDelta > REC_DELTA) { + var inc = (int)(mRecRemainDelta / REC_DELTA); + mRecStateCursor += inc; + mRecRemainDelta -= inc * REC_DELTA; + } + + // check cursor + if (mRecStateCursor >= mRecStates.Length - 1) { + // try to load next sector + var remain = mRecStates.Length - mRecStateCursor; + + lock (mStatusLock) { + mIsReady = false; + } + Task.Run(() => { + LoadData(); + mRecStateCursor = remain; + mRecTrafoCursor = 0; + }); + + // return directly until next sector load successfully + return; + } + + // try shift trafo cursor + while ((mRecTrafoCursor < mRecTrafos.Length - 1) && mRecTrafos[mRecTrafoCursor + 1].Frame <= mRecStateCursor) { + mRecTrafoCursor++; + } + + // otherwise, output data + RecNewBallState?.Invoke(new RecBallStateReport() { + BallState = mRecStates[mRecStateCursor].Slerp(mRecStates[mRecStateCursor + 1], mRecRemainDelta / REC_DELTA), + BallType = mRecTrafos[mRecTrafoCursor].TrafoType, + Identifier = 0 + }); + } + + public string GetProfile() { + lock (mStatusLock) { + return $@"Rec folder: {mFileFolder} +Rec folder cursor: {mFileCursor} +Rec folder type: {mFileType} +Rec ready/play status: {mIsReady}/{mIsPlaying} +SubRec HS/SR: {mRecHSScore}/{mRecSRScore} +SubRec state/trafo size: {mRecStates?.Length}/{mRecTrafos?.Length}"; + } } } } diff --git a/src/scripts/cores/utils/BallStates.cs b/src/scripts/cores/utils/BallStates.cs index 69de133..0af3618 100644 --- a/src/scripts/cores/utils/BallStates.cs +++ b/src/scripts/cores/utils/BallStates.cs @@ -1,30 +1,144 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace BallanceStalker.Cores.Utils { - public enum BallType : UInt32 { + public enum VxBallType : UInt32 { Stone, Wood, Paper } - public struct VxVector { - float X, Y, Z; - } - public struct VxQuaternion { - float X, Y, Z, W; + public static class QuaternionHelper { + public static float Dot(this System.Numerics.Quaternion f, System.Numerics.Quaternion p_to) { + return f.X * p_to.X + f.Y * p_to.Y + f.Z * p_to.Z + f.W * p_to.W; + } + public static System.Numerics.Quaternion Slerp(this System.Numerics.Quaternion f, System.Numerics.Quaternion p_to, float delta) { + float omega, cosom, sinom, scale0, scale1; + + // calc cosine + cosom = f.Dot(p_to); + + // adjust signs (if necessary) + bool is_minus = cosom < 0f; + if (is_minus) cosom = -cosom; + + // calculate coefficients + if ((1f - cosom) >= 0.01f) { + // standard case (slerp) + omega = (float)Math.Acos(cosom); + sinom = (float)Math.Sin(omega); + scale0 = (float)Math.Sin((1.0 - delta) * omega) / sinom; + scale1 = (float)Math.Sin(delta * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0f - delta; + scale1 = delta; + } + + // adjust signs (if necessary) + if (is_minus) scale1 = -scale1; + + return f * scale0 + p_to * scale1; + } } + + //public struct VxVector { + // public VxVector(float x, float y, float z) { + // X = x; + // Y = y; + // Z = z; + // } + // public float X; + // public float Y; + // public float Z; + + // public static VxVector operator+(VxVector a, VxVector b) { + // return new VxVector(a.X + b.X, a.Y + b.Y, a.Z + b.Z); + // } + // public static VxVector operator -(VxVector a, VxVector b) { + // return new VxVector(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + // } + // public static VxVector operator *(float m, VxVector a) { + // return new VxVector(m * a.X, m * a.Y, m * a.Z); + // } + // public static VxVector operator *(VxVector a, float m) { + // return new VxVector(m * a.X, m * a.Y, m * a.Z); + // } + // public static VxVector operator /(VxVector a, float m) { + // return new VxVector(a.X / m, a.Y / m, a.Z / m); + // } + //} + //public struct VxQuaternion { + // public VxQuaternion(float x, float y, float z, float w) { + // X = x; + // Y = y; + // Z = z; + // W = w; + // } + // public float X; + // public float Y; + // public float Z; + // public float W; + // public float Dot(VxQuaternion p_to) { + // return X * p_to.X + Y * p_to.Y + Z * p_to.Z + W * p_to.W; + // } + // // Reference: https://github.com/godotengine/godot/blob/master/core/math/quaternion.cpp + // public VxQuaternion Slerp(VxQuaternion p_to, float delta) { + // float omega, cosom, sinom, scale0, scale1; + + // // calc cosine + // cosom = Dot(p_to); + + // // adjust signs (if necessary) + // bool is_minus = cosom < 0f; + // if (is_minus) cosom = -cosom; + + // // calculate coefficients + // if ((1f - cosom) >= 0.01f) { + // // standard case (slerp) + // omega = (float)Math.Acos(cosom); + // sinom = (float)Math.Sin(omega); + // scale0 = (float)Math.Sin((1.0 - delta) * omega) / sinom; + // scale1 = (float)Math.Sin(delta * omega) / sinom; + // } else { + // // "from" and "to" quaternions are very close + // // ... so we can do a linear interpolation + // scale0 = 1.0f - delta; + // scale1 = delta; + // } + + // // adjust signs (if necessary) + // if (is_minus) scale1 = -scale1; + + // return new VxQuaternion( + // scale0 * X + scale1 * p_to.X, + // scale0 * Y + scale1 * p_to.Y, + // scale0 * Z + scale1 * p_to.Z, + // scale0 * W + scale1 * p_to.W + // ); + // } + + //} public struct VxBallState { - VxVector Pos; - VxQuaternion Quat; + public System.Numerics.Vector3 Pos; + public System.Numerics.Quaternion Quat; + public VxBallState Slerp(VxBallState to, float delta) { + return new VxBallState() { + Pos = (to.Pos - this.Pos) * delta + this.Pos, + Quat = this.Quat.Slerp(to.Quat, delta) + }; + } } public struct RecBallStateReport { - long Identifier; - VxBallState BallState; + public long Identifier; + public VxBallType BallType; + public VxBallState BallState; } } diff --git a/src/scripts/cores/utils/IRec.cs b/src/scripts/cores/utils/IRec.cs index 167007a..5113f4d 100644 --- a/src/scripts/cores/utils/IRec.cs +++ b/src/scripts/cores/utils/IRec.cs @@ -6,18 +6,23 @@ using System.Threading.Tasks; namespace BallanceStalker.Cores.Utils { public interface IRec { + void Startup(); + void Shutdown(); void Play(); void Pause(); void Seek(float sec); - void Tick(float delta_sec, List report); + void Tick(float delta_sec); + string GetProfile(); /// /// register a ball /// long is identifier, string is name /// event Action RecRegisterBall; - event Action RecPlaying; - event Action RecPaused; + event Action RecNewBallState; + event Action> RecNewBallStates; + //event Action RecPlaying; + //event Action RecPaused; /// /// unregister a ball /// long is identifier