add spirit trail rec reader
This commit is contained in:
parent
6deaa4c022
commit
21ff253337
|
@ -3,6 +3,8 @@
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net472</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Brigadier.NET" Version="1.2.13" />
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.21.1" />
|
<PackageReference Include="Google.Protobuf" Version="3.21.1" />
|
||||||
|
<PackageReference Include="zlib.net" Version="1.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using BallanceStalker.Cores.Utils;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -6,5 +7,23 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BallanceStalker.Cores.Managers {
|
namespace BallanceStalker.Cores.Managers {
|
||||||
public class RecManager {
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,51 +5,217 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using zlib;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace BallanceStalker.Cores.Rec {
|
namespace BallanceStalker.Cores.Rec {
|
||||||
|
|
||||||
|
public struct SpiritTrailTrafo {
|
||||||
|
public int Frame;
|
||||||
|
public VxBallType TrafoType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SpiritTrailRecType {
|
||||||
|
HS,
|
||||||
|
SR
|
||||||
|
}
|
||||||
|
|
||||||
public class SpiritTrailRec : Utils.IRec {
|
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;
|
mIsReady = mIsPlaying = false;
|
||||||
Task.Run(() => {
|
|
||||||
// load data
|
|
||||||
|
|
||||||
|
mFileFolder = rec_folder;
|
||||||
|
mFileType = t;
|
||||||
|
mFileCursor = 1;
|
||||||
|
|
||||||
lock (mStatusLock) {
|
mRecStateCursor = mRecTrafoCursor = 0;
|
||||||
mIsReady = true;
|
mRecRemainDelta = 0f;
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<long, string> RecRegisterBall;
|
public event Action<long, string> RecRegisterBall;
|
||||||
public event Action RecPlaying;
|
|
||||||
public event Action RecPaused;
|
|
||||||
public event Action<long> RecUnregisterBall;
|
public event Action<long> RecUnregisterBall;
|
||||||
|
public event Action<RecBallStateReport> RecNewBallState;
|
||||||
|
public event Action<List<RecBallStateReport>> RecNewBallStates;
|
||||||
|
|
||||||
object mStatusLock;
|
object mStatusLock;
|
||||||
bool mIsReady, mIsPlaying;
|
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() {
|
public void Pause() {
|
||||||
lock (mStatusLock) {
|
lock (mStatusLock) {
|
||||||
mIsPlaying = false;
|
mIsPlaying = false;
|
||||||
}
|
}
|
||||||
|
//RecPaused?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Play() {
|
public void Play() {
|
||||||
lock (mStatusLock) {
|
lock (mStatusLock) {
|
||||||
mIsPlaying = true;
|
mIsPlaying = true;
|
||||||
}
|
}
|
||||||
|
//RecPlaying?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Seek(float sec) {
|
public void Seek(float sec) {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick(float delta_sec, List<RecBallStateReport> report) {
|
public void Tick(float delta_sec) {
|
||||||
lock (mStatusLock) {
|
lock (mStatusLock) {
|
||||||
if (!mIsPlaying || !mIsReady) return;
|
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}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,144 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BallanceStalker.Cores.Utils {
|
namespace BallanceStalker.Cores.Utils {
|
||||||
public enum BallType : UInt32 {
|
public enum VxBallType : UInt32 {
|
||||||
Stone,
|
Stone,
|
||||||
Wood,
|
Wood,
|
||||||
Paper
|
Paper
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct VxVector {
|
public static class QuaternionHelper {
|
||||||
float X, Y, Z;
|
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 {
|
public struct VxBallState {
|
||||||
VxVector Pos;
|
public System.Numerics.Vector3 Pos;
|
||||||
VxQuaternion Quat;
|
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 {
|
public struct RecBallStateReport {
|
||||||
long Identifier;
|
public long Identifier;
|
||||||
VxBallState BallState;
|
public VxBallType BallType;
|
||||||
|
public VxBallState BallState;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,23 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BallanceStalker.Cores.Utils {
|
namespace BallanceStalker.Cores.Utils {
|
||||||
public interface IRec {
|
public interface IRec {
|
||||||
|
void Startup();
|
||||||
|
void Shutdown();
|
||||||
void Play();
|
void Play();
|
||||||
void Pause();
|
void Pause();
|
||||||
void Seek(float sec);
|
void Seek(float sec);
|
||||||
void Tick(float delta_sec, List<RecBallStateReport> report);
|
void Tick(float delta_sec);
|
||||||
|
string GetProfile();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// register a ball
|
/// register a ball
|
||||||
/// long is identifier, string is name
|
/// long is identifier, string is name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<long, string> RecRegisterBall;
|
event Action<long, string> RecRegisterBall;
|
||||||
event Action RecPlaying;
|
event Action<RecBallStateReport> RecNewBallState;
|
||||||
event Action RecPaused;
|
event Action<List<RecBallStateReport>> RecNewBallStates;
|
||||||
|
//event Action RecPlaying;
|
||||||
|
//event Action RecPaused;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// unregister a ball
|
/// unregister a ball
|
||||||
/// long is identifier
|
/// long is identifier
|
||||||
|
|
Loading…
Reference in New Issue
Block a user