325 lines
12 KiB
C#
325 lines
12 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using System.Linq;
|
|
using System;
|
|
using UnityEngine.UI;
|
|
using Postgrest;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine.Rendering.Universal;
|
|
using System.Globalization;
|
|
using Oculus.Interaction;
|
|
using System.Reactive;
|
|
using Postgrest.Interfaces;
|
|
|
|
namespace GhostSystem
|
|
{
|
|
public class GhostReplayManager : MonoBehaviour
|
|
{
|
|
[Header("Replay Settings")]
|
|
public string sceneId;
|
|
public string UserFilter = null; // optional: filtere nach User-ID
|
|
public bool Loop = true;
|
|
public DateTime selectedStartTime;
|
|
public DateSelector dateSelector;
|
|
|
|
[Tooltip("Optional: Begrenze die Frame-Abfrage auf einen Zeitraum ab Startzeit.")]
|
|
public bool LimitTimeRange = true;
|
|
|
|
[Tooltip("Zeitraum in Minuten, der ab Startzeit ausgewertet wird.")]
|
|
public int TimeRangeMinutes = 15;
|
|
|
|
[Header("Target Objects")]
|
|
public List<TrackedObjectBinding> TrackedObjects;
|
|
|
|
[Header("Playback")]
|
|
public float PlaybackSpeed = 1f;
|
|
public bool AutoStart = true;
|
|
public Slider playbackSlider;
|
|
private bool isScrubbing = false;
|
|
|
|
private bool isPlaying = false;
|
|
private float playbackTime = 0f;
|
|
private float totalDuration = 0f;
|
|
private string firstTimestamp = "";
|
|
private string lastTimestamp = "";
|
|
|
|
private Dictionary<string, List<FrameModel>> objectTracks = new();
|
|
|
|
private bool supabaseReady = false;
|
|
private bool sceneIdReady = false;
|
|
private string loadedSceneId;
|
|
private int doorCount;
|
|
|
|
private DateTime latestLoadedTimestamp;
|
|
|
|
[Serializable]
|
|
public class TrackedObjectBinding
|
|
{
|
|
public string objectId;
|
|
public GameObject targetObject;
|
|
}
|
|
|
|
private async void OnEnable()
|
|
{
|
|
SupabaseManager.OnSuperbaseReady += HandleSupabaseReady;
|
|
RoomManager.OnSceneIdReady += HandleSceneIdReady;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
SupabaseManager.OnSuperbaseReady -= HandleSupabaseReady;
|
|
RoomManager.OnSceneIdReady -= HandleSceneIdReady;
|
|
}
|
|
|
|
private void HandleSupabaseReady()
|
|
{
|
|
supabaseReady = true;
|
|
TryInitializeReplay();
|
|
}
|
|
|
|
private void HandleSceneIdReady(string id, List<GameObject> doors)
|
|
{
|
|
sceneIdReady = true;
|
|
loadedSceneId = id;
|
|
foreach (GameObject door in doors)
|
|
{
|
|
TrackedObjectBinding trackedObjectBinding = new TrackedObjectBinding();
|
|
trackedObjectBinding.objectId = "Door";
|
|
trackedObjectBinding.targetObject = door.GetComponentInChildren<OneGrabRotateTransformer>().gameObject;
|
|
TrackedObjects.Add(trackedObjectBinding);
|
|
doorCount++;
|
|
}
|
|
TryInitializeReplay();
|
|
}
|
|
|
|
private async UniTaskVoid TryInitializeReplay()
|
|
{
|
|
if (!supabaseReady || !sceneIdReady) return;
|
|
|
|
sceneId = loadedSceneId;
|
|
|
|
await LoadAllFrames();
|
|
StartReplay();
|
|
_ = PeriodicUpdateFrames();
|
|
}
|
|
|
|
public async UniTask LoadAllFrames()
|
|
{
|
|
Debug.Log("SceneId used in query: " + sceneId);
|
|
Debug.Log("StartTime filter: " + dateSelector.selectedDateTime().ToString("o"));
|
|
Debug.Log("EndTime filter: " + dateSelector.EndTime().ToString("o"));
|
|
|
|
var startTime = dateSelector.selectedDateTime();
|
|
var endTime = dateSelector.EndTime();
|
|
|
|
objectTracks.Clear();
|
|
int batchSize = 1000;
|
|
int offset = 0;
|
|
bool more = true;
|
|
List<FrameModel> allFrames = new();
|
|
|
|
while (more)
|
|
{
|
|
List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
|
|
timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTime.ToString("o")));
|
|
timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o")));
|
|
|
|
var query = SupabaseManager.instance.supabase
|
|
.From<FrameModel>()
|
|
.Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId)
|
|
//.Where(x => x.Timestamp >= "timestamp '2025-06-14 10:27:00' and" x => x.Timestamp.timestamp <= "timestamp '2025-06-14 10:29:00'")
|
|
//.Filter("timestamp", Postgrest.Constants.Operator.GreaterThanOrEqual, startTime.ToString("o"))
|
|
.And(timestampFilter)
|
|
//.Filter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o"))
|
|
.Order("timestamp", Postgrest.Constants.Ordering.Ascending)
|
|
.Limit(batchSize) // Limit, wie viele Frames in einem Batch geladen werden. (Standardmäßig 1000)
|
|
.Offset(offset); // Um zu wissen, wo der nächste Batch anfängt
|
|
|
|
if (!string.IsNullOrEmpty(UserFilter))
|
|
query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter);
|
|
|
|
var result = await query.Get();
|
|
if (result.Models.Count == 0) break;
|
|
if (result.Models[0].SceneId != sceneId) break;
|
|
Debug.LogError("Model Count: " + result.Models.Count);
|
|
allFrames.AddRange(result.Models);
|
|
offset += batchSize;
|
|
more = result.Models.Count == batchSize;
|
|
}
|
|
|
|
foreach (var frame in allFrames)
|
|
{
|
|
if (!objectTracks.ContainsKey(frame.ObjectId))
|
|
objectTracks[frame.ObjectId] = new List<FrameModel>();
|
|
objectTracks[frame.ObjectId].Add(frame);
|
|
}
|
|
|
|
if (allFrames.Count > 1)
|
|
{
|
|
latestLoadedTimestamp = allFrames.Last().TimestampParsed;
|
|
totalDuration = (float)(latestLoadedTimestamp - allFrames.First().TimestampParsed).TotalSeconds;
|
|
firstTimestamp = allFrames.First().TimestampParsed.ToLongTimeString();
|
|
lastTimestamp = latestLoadedTimestamp.ToLongTimeString();
|
|
}
|
|
|
|
Debug.LogWarning($"Loaded {allFrames.Count} frames across {objectTracks.Count} objects.");
|
|
}
|
|
|
|
private async UniTaskVoid PeriodicUpdateFrames() // lädt alle 10 s neue Frames nach, falls verfügbar
|
|
{
|
|
while (true)
|
|
{
|
|
await UniTask.Delay(TimeSpan.FromSeconds(10));
|
|
await LoadNewFramesAfter(latestLoadedTimestamp);
|
|
}
|
|
}
|
|
|
|
private async UniTask LoadNewFramesAfter(DateTime afterTime) // lädt einen neuen Batch nach.
|
|
{
|
|
var endTime = dateSelector.EndTime();
|
|
|
|
List<IPostgrestQueryFilter> timestampFilter = new List<IPostgrestQueryFilter>();
|
|
timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.GreaterThan, afterTime.ToString("o")));
|
|
timestampFilter.Add(new QueryFilter("timestamp", Postgrest.Constants.Operator.LessThan, endTime.ToString("o")));
|
|
|
|
var query = SupabaseManager.instance.supabase
|
|
.From<FrameModel>()
|
|
.Filter("scene_id", Postgrest.Constants.Operator.Equals, sceneId)
|
|
.And(timestampFilter)
|
|
//.Filter("timestamp", Postgrest.Constants.Operator.GreaterThan, afterTime.ToString("o"))
|
|
//.Filter("timestamp", Postgrest.Constants.Operator.LessThanOrEqual, endTime.ToString("o"))
|
|
.Order("timestamp", Postgrest.Constants.Ordering.Ascending);
|
|
|
|
if (!string.IsNullOrEmpty(UserFilter))
|
|
query = query.Filter("user_id", Postgrest.Constants.Operator.Equals, UserFilter);
|
|
|
|
var result = await query.Get();
|
|
var newFrames = result.Models;
|
|
|
|
foreach (var frame in newFrames)
|
|
{
|
|
if (!objectTracks.ContainsKey(frame.ObjectId))
|
|
objectTracks[frame.ObjectId] = new List<FrameModel>();
|
|
objectTracks[frame.ObjectId].Add(frame);
|
|
}
|
|
|
|
if (newFrames.Count > 0)
|
|
{
|
|
latestLoadedTimestamp = newFrames.Last().TimestampParsed;
|
|
totalDuration = (float)(latestLoadedTimestamp - objectTracks.Values.SelectMany(f => f).First().TimestampParsed).TotalSeconds;
|
|
}
|
|
|
|
Debug.Log($"Appended {newFrames.Count} new frames.");
|
|
}
|
|
|
|
|
|
public void StartReplay() // Für das Play UI-Element, um das Replay zu starten
|
|
{
|
|
isPlaying = true;
|
|
playbackTime = 0f;
|
|
Debug.LogError("Replay started: total duration = " + totalDuration + " s, First: " + firstTimestamp + ", Last: " + lastTimestamp);
|
|
}
|
|
|
|
public void PauseReplay() => isPlaying = !isPlaying;
|
|
|
|
public void StopReplay() // Für das Stop UI-Element, um das Replay zu beenden
|
|
{
|
|
isPlaying = false;
|
|
playbackTime = 0f;
|
|
}
|
|
|
|
public void SetPlaybackTime(float time) // Für das Slider UI-Element zum Scrubben
|
|
{
|
|
playbackTime = Mathf.Clamp(time, 0f, totalDuration);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!isPlaying || objectTracks.Count == 0) return;
|
|
|
|
playbackTime += Time.deltaTime * PlaybackSpeed;
|
|
|
|
if (playbackTime > totalDuration)
|
|
{
|
|
if (Loop)
|
|
playbackTime = 0f;
|
|
else
|
|
isPlaying = false;
|
|
}
|
|
|
|
DateTime currentTimestamp = objectTracks.Values
|
|
.SelectMany(list => list)
|
|
.First().TimestampParsed + TimeSpan.FromSeconds(playbackTime);
|
|
|
|
foreach (var binding in TrackedObjects)
|
|
{
|
|
if (!objectTracks.ContainsKey(binding.objectId)) continue;
|
|
|
|
var frames = objectTracks[binding.objectId];
|
|
if (frames.Count < 2) continue;
|
|
|
|
FrameModel a = null, b = null;
|
|
|
|
for (int i = 0; i < frames.Count - 1; i++)
|
|
{
|
|
var tA = frames[i].TimestampParsed;
|
|
var tB = frames[i + 1].TimestampParsed;
|
|
|
|
if (tA <= currentTimestamp && tB >= currentTimestamp)
|
|
{
|
|
a = frames[i];
|
|
b = frames[i + 1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (a != null && b != null)
|
|
{
|
|
double tA = a.TimestampParsed.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
|
double tB = b.TimestampParsed.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
|
double tCurrent = currentTimestamp.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
|
|
|
float t = Mathf.Clamp01((float)((tCurrent - tA) / (tB - tA)));
|
|
|
|
Vector3 pos = Vector3.Lerp(a.Position, b.Position, t);
|
|
Quaternion rot = Quaternion.Slerp(a.Rotation, b.Rotation, t);
|
|
|
|
binding.targetObject.transform.SetPositionAndRotation(pos, rot);
|
|
}
|
|
}
|
|
|
|
if (!isScrubbing && playbackSlider != null && totalDuration > 0f)
|
|
{
|
|
playbackSlider.value = playbackTime / totalDuration;
|
|
}
|
|
}
|
|
|
|
public void SetSliderScrubValue(float sliderValue)
|
|
{
|
|
if (isScrubbing)
|
|
SetPlaybackTime(sliderValue * totalDuration);
|
|
}
|
|
|
|
|
|
public void OnSliderStartDrag()
|
|
{
|
|
isScrubbing = true;
|
|
}
|
|
|
|
public void OnSliderEndDrag()
|
|
{
|
|
isScrubbing = false;
|
|
SetPlaybackTime(playbackSlider.value * totalDuration);
|
|
}
|
|
|
|
public void RemoveDoors()
|
|
{
|
|
TrackedObjects.RemoveRange(TrackedObjects.Count - doorCount, doorCount);
|
|
doorCount = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|