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