add spirit trail rec reader
This commit is contained in:
parent
6deaa4c022
commit
21ff253337
|
@ -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>
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user