add spirit trail rec reader

This commit is contained in:
yyc12345 2022-09-04 15:23:19 +08:00
parent 6deaa4c022
commit 21ff253337
5 changed files with 331 additions and 25 deletions

View File

@ -3,6 +3,8 @@
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Brigadier.NET" Version="1.2.13" />
<PackageReference Include="Google.Protobuf" Version="3.21.1" />
<PackageReference Include="zlib.net" Version="1.0.4" />
</ItemGroup>
</Project>

View File

@ -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<Guid, IRec> RecAdded;
public event Action<Guid, IRec> RecRemoved;
private Dictionary<Guid, IRec> mRecDict = new Dictionary<Guid, IRec>();
public Guid AddSpiritTrailRec(string rec_folder) {
}
public void RemoveRec(Guid uuid) {
}
public void Tick(float delta) {
}
}
}

View File

@ -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 class SpiritTrailRec : Utils.IRec {
public SpiritTrailRec(string rec_folder) {
mIsReady = mIsPlaying = false;
Task.Run(() => {
// load data
lock (mStatusLock) {
mIsReady = true;
public struct SpiritTrailTrafo {
public int Frame;
public VxBallType TrafoType;
}
});
public enum SpiritTrailRecType {
HS,
SR
}
public class SpiritTrailRec : Utils.IRec {
static readonly float REC_DELTA = 1f / 8f;
public SpiritTrailRec(string rec_folder, SpiritTrailRecType t) {
mIsReady = mIsPlaying = false;
mFileFolder = rec_folder;
mFileType = t;
mFileCursor = 1;
mRecStateCursor = mRecTrafoCursor = 0;
mRecRemainDelta = 0f;
}
public event Action<long, string> RecRegisterBall;
public event Action RecPlaying;
public event Action RecPaused;
public event Action<long> RecUnregisterBall;
public event Action<RecBallStateReport> RecNewBallState;
public event Action<List<RecBallStateReport>> 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<RecBallStateReport> 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}";
}
}
}
}

View File

@ -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 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 struct VxQuaternion {
float X, Y, Z, 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;
}
}

View File

@ -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<RecBallStateReport> report);
void Tick(float delta_sec);
string GetProfile();
/// <summary>
/// register a ball
/// long is identifier, string is name
/// </summary>
event Action<long, string> RecRegisterBall;
event Action RecPlaying;
event Action RecPaused;
event Action<RecBallStateReport> RecNewBallState;
event Action<List<RecBallStateReport>> RecNewBallStates;
//event Action RecPlaying;
//event Action RecPaused;
/// <summary>
/// unregister a ball
/// long is identifier