diff --git a/.gitignore b/.gitignore index 54ee86d..aaf39a5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ Contact_Information.patch Runtime/Prefabs/.DS_Store Contact_Information.patch.meta + + +# development environments +.vs diff --git a/Runtime/Scripts/Core/GeometrySequenceStream.cs b/Runtime/Scripts/Core/GeometrySequenceStream.cs index 2b39a7c..64c7feb 100644 --- a/Runtime/Scripts/Core/GeometrySequenceStream.cs +++ b/Runtime/Scripts/Core/GeometrySequenceStream.cs @@ -15,349 +15,354 @@ namespace BuildingVolumes.Player { - public class GeometrySequenceStream : MonoBehaviour - { - public string pathToSequence { get; private set; } - - public Bounds drawBounds = new Bounds(Vector3.zero, new Vector3(3, 3, 3)); - - //Buffering options - public int bufferSize = 30; - public bool useAllThreads = true; - public int threadCount = 4; - - //Debug - public bool attachFrameDebugger = false; - public GSFrameDebugger frameDebugger = null; - - //Materials - public Material customMaterial; - public bool instantiateMaterial = true; - public MaterialProperties materialSlots = MaterialProperties.Albedo; - public List customMaterialSlots; - - //Pointcloud rendering options - public PointcloudRenderPath pointRenderPath = PointcloudRenderPath.PolySpatial; - public float pointSize = 0.02f; - public float pointEmission = 1f; - - //Mesh and pointcloud rendering - [HideInInspector] - public BufferedGeometryReader bufferedReader; - public IPointCloudRenderer pointcloudRenderer; - public IMeshSequenceRenderer meshSequenceRenderer; - - //Thumbnail rendering - BufferedGeometryReader thumbnailReader; - IPointCloudRenderer thumbnailPCRenderer; - IMeshSequenceRenderer thumbnailMeshRenderer; - - //Buffering - public bool readerInitialized = false; - public bool frameDropped = false; - public int framesDroppedCounter = 0; - public int lastFrameBufferIndex = 0; - public float targetFrameTimeMs = 0; - public float lastFrameTime = 0; - public int lastFrameIndex; - public float sequenceDeltaTime = 0; - public float elapsedMsSinceSequenceStart = 0; - public float smoothedFPS = 0f; - - //Performance tracking - float sequenceStartTime = 0; - float lastSequenceCompletionTime; - - public enum PathType { AbsolutePath, RelativeToDataPath, RelativeToPersistentDataPath, RelativeToStreamingAssets }; - public enum PointcloudRenderPath { Shadergraph, Legacy, PolySpatial }; - [Flags] public enum MaterialProperties { Albedo = 1, Emission = 2, Detail = 4 } - - private void Awake() + public class GeometrySequenceStream : MonoBehaviour { + public string pathToSequence { get; private set; } + + public Bounds drawBounds = new Bounds(Vector3.zero, new Vector3(3, 3, 3)); + + //Buffering options + public int bufferSize = 30; + public bool useAllThreads = true; + public int threadCount = 4; + + //Debug + public bool attachFrameDebugger = false; + public GSFrameDebugger frameDebugger = null; + + //Materials + public Material customMaterial; + public bool instantiateMaterial = true; + public MaterialProperties materialSlots = MaterialProperties.Albedo; + public List customMaterialSlots; + + //Pointcloud rendering options + public PointcloudRenderPath pointRenderPath = PointcloudRenderPath.PolySpatial; + public float pointSize = 0.02f; + public float pointEmission = 1f; + + //Mesh and pointcloud rendering + [HideInInspector] + public BufferedGeometryReader bufferedReader; + public IPointCloudRenderer pointcloudRenderer; + public IMeshSequenceRenderer meshSequenceRenderer; + + //Thumbnail rendering + BufferedGeometryReader thumbnailReader; + IPointCloudRenderer thumbnailPCRenderer; + IMeshSequenceRenderer thumbnailMeshRenderer; + + //Buffering + public bool readerInitialized = false; + public bool frameDropped = false; + public int framesDroppedCounter = 0; + public int lastFrameBufferIndex = 0; + public float targetFrameTimeMs = 0; + public float lastFrameTime = 0; + public int lastFrameIndex; + public float sequenceDeltaTime = 0; + public float elapsedMsSinceSequenceStart = 0; + public float smoothedFPS = 0f; + + //Performance tracking + float sequenceStartTime = 0; + float lastSequenceCompletionTime; + + public enum PathType { AbsolutePath, RelativeToDataPath, RelativeToPersistentDataPath, RelativeToStreamingAssets }; + public enum PointcloudRenderPath { Shadergraph, Legacy, PolySpatial }; + [Flags] public enum MaterialProperties { Albedo = 1, Emission = 2, Detail = 4 } + + private void Awake() + { #if !UNITY_EDITOR && !UNITY_STANDALONE_WIN && !UNITY_STANDALONE_OSX && !UNITY_STANDALONE_LINUX && !UNITY_IOS && !UNITY_ANDROID && !UNITY_TVOS && !UNITY_VISIONOS Debug.LogError("Platform not supported by Geometry Sequence Streamer! Playback will probably fail"); #endif - if (!useAllThreads) - JobsUtility.JobWorkerCount = threadCount; - else - JobsUtility.JobWorkerCount = JobsUtility.JobWorkerMaximumCount; + if (!useAllThreads) + JobsUtility.JobWorkerCount = threadCount; + else + JobsUtility.JobWorkerCount = JobsUtility.JobWorkerMaximumCount; - if (attachFrameDebugger) - AttachFrameDebugger(); - } + if (attachFrameDebugger) + AttachFrameDebugger(); + } - /// - /// Cleans up the current sequence and prepares the playback of the sequence in the given folder. Doesn't start playback! - /// - /// The absolute path to the folder containing a sequence of .ply geometry files and optionally .dds texture files - public bool ChangeSequence(string absolutePathToSequence, float playbackFPS) - { - Dispose(); - lastFrameIndex = -1; - lastFrameBufferIndex = -1; + /// + /// Cleans up the current sequence and prepares the playback of the sequence in the given folder. Doesn't start playback! + /// + /// The absolute path to the folder containing a sequence of .ply geometry files and optionally .dds texture files + public bool ChangeSequence(string absolutePathToSequence, float playbackFPS) + { + Dispose(); + lastFrameIndex = -1; + lastFrameBufferIndex = -1; - pathToSequence = absolutePathToSequence; - bufferedReader = new BufferedGeometryReader(); - if (!bufferedReader.SetupReader(pathToSequence, bufferSize)) - return readerInitialized; + pathToSequence = absolutePathToSequence; + bufferedReader = new BufferedGeometryReader(); + if (!bufferedReader.SetupReader(pathToSequence, bufferSize)) + return readerInitialized; -//On Polyspatial, we force the render path to a special variant for this system + //On Polyspatial, we force the render path to a special variant for this system #if UNITY_VISIONOS UnityEngine.Object volumeCam = UnityEngine.Object.FindFirstObjectByType(); if (volumeCam != null) pointRenderPath = PointcloudRenderPath.PolySpatial; #endif - if (bufferedReader.sequenceConfig.geometryType == SequenceConfiguration.GeometryType.Point) - pointcloudRenderer = SetupPointcloudRenderer(bufferedReader, pointRenderPath); - else - meshSequenceRenderer = SetupMeshSequenceRenderer(bufferedReader, pointRenderPath); - - targetFrameTimeMs = 1000f / (float)playbackFPS; - smoothedFPS = playbackFPS; - - readerInitialized = true; - return readerInitialized; - } - - - public void UpdateFrame() - { - if (!readerInitialized) - return; - - if (bufferedReader.totalFrames == 1) - { - bufferedReader.SetupFrameForReading(bufferedReader.frameBuffer[0], bufferedReader.sequenceConfig, 0); - bufferedReader.ScheduleGeometryReadJob(bufferedReader.frameBuffer[0], bufferedReader.GetDeviceDependentTexturePath(0)); - bufferedReader.frameBuffer[0].geoJobHandle.Complete(); - ShowFrame(bufferedReader.frameBuffer[0]); - return; - } - - - sequenceDeltaTime += Time.deltaTime * 1000; - elapsedMsSinceSequenceStart += Time.deltaTime * 1000; - if (elapsedMsSinceSequenceStart > targetFrameTimeMs * bufferedReader.totalFrames) //If we wrap around our ring buffer - { - elapsedMsSinceSequenceStart -= targetFrameTimeMs * bufferedReader.totalFrames; - - //For performance tracking - lastSequenceCompletionTime = (Time.time - sequenceStartTime) * lastSequenceCompletionTime; - sequenceStartTime = Time.time; - framesDroppedCounter = 0; - } - - int targetFrameIndex = Mathf.RoundToInt(elapsedMsSinceSequenceStart / targetFrameTimeMs); - - //Check how many frames our targetframe is in advance relative to the last played frame - int framesInAdvance = 0; - if (targetFrameIndex > lastFrameIndex) - framesInAdvance = targetFrameIndex - lastFrameIndex; + if (bufferedReader.sequenceConfig.geometryType == SequenceConfiguration.GeometryType.Point) + pointcloudRenderer = SetupPointcloudRenderer(bufferedReader, pointRenderPath); + else + meshSequenceRenderer = SetupMeshSequenceRenderer(bufferedReader, pointRenderPath); - if (targetFrameIndex < lastFrameIndex) - framesInAdvance = (bufferedReader.totalFrames - lastFrameIndex) + targetFrameIndex; + this.SetPlaybackFPS(playbackFPS); - frameDropped = framesInAdvance > 1 ? true : false; - if (frameDropped) - framesDroppedCounter += framesInAdvance - 1; - - //Debug.Log("Elapsed MS in sequence: " + elapsedMsSinceSequenceStart + ", target Index: " + targetFrameIndex + ", last frame: " + lastFrameIndex + ", target in advance: " + targetFrameIndex); + readerInitialized = true; + return readerInitialized; + } - bufferedReader.BufferFrames(targetFrameIndex, lastFrameIndex); - if (framesInAdvance > 0) - { - //Check if our desired frame is inside the frame buffer and loaded, so that we can use it - int newBufferIndex = bufferedReader.GetBufferIndexForLoadedPlaybackIndex(targetFrameIndex); - - //Is the frame inside the buffer and fully loaded? - if (newBufferIndex > -1) + public void UpdateFrame() { - //Now that we a show a new frame, we mark the old played frame as consumed, and the new frame as playing - if (lastFrameBufferIndex > -1) - bufferedReader.frameBuffer[lastFrameBufferIndex].bufferState = BufferState.Consumed; - - bufferedReader.frameBuffer[newBufferIndex].bufferState = BufferState.Playing; - ShowFrame(bufferedReader.frameBuffer[newBufferIndex]); - lastFrameBufferIndex = newBufferIndex; - lastFrameIndex = targetFrameIndex; - - //Sometimes, the system might struggle to render a frame, or the application has a low framerate in general - //For performance tracking, we need to decouple the application framerate from our stream framerate. - //If we are lagging behind due to render reasons, but have sucessfully buffered up to the current target frame - //we still hit our target time window and the stream is performing well - //Therefore we substract the dropped frames from our deltatime - - if (frameDropped && framesInAdvance > 1) - sequenceDeltaTime -= (framesInAdvance - 1) * targetFrameTimeMs; - - float decay = 0.9f; - smoothedFPS = decay * smoothedFPS + (1.0f - decay) * (1000f / sequenceDeltaTime); - - lastFrameTime = sequenceDeltaTime; - sequenceDeltaTime = 0; + if (!readerInitialized) + return; + + if (bufferedReader.totalFrames == 1) + { + bufferedReader.SetupFrameForReading(bufferedReader.frameBuffer[0], bufferedReader.sequenceConfig, 0); + bufferedReader.ScheduleGeometryReadJob(bufferedReader.frameBuffer[0], bufferedReader.GetDeviceDependentTexturePath(0)); + bufferedReader.frameBuffer[0].geoJobHandle.Complete(); + ShowFrame(bufferedReader.frameBuffer[0]); + return; + } + + + sequenceDeltaTime += Time.deltaTime * 1000; + elapsedMsSinceSequenceStart += Time.deltaTime * 1000; + if (elapsedMsSinceSequenceStart > targetFrameTimeMs * bufferedReader.totalFrames) //If we wrap around our ring buffer + { + elapsedMsSinceSequenceStart -= targetFrameTimeMs * bufferedReader.totalFrames; + + //For performance tracking + lastSequenceCompletionTime = (Time.time - sequenceStartTime) * lastSequenceCompletionTime; + sequenceStartTime = Time.time; + framesDroppedCounter = 0; + } + + int targetFrameIndex = Mathf.RoundToInt(elapsedMsSinceSequenceStart / targetFrameTimeMs); + + //Check how many frames our targetframe is in advance relative to the last played frame + int framesInAdvance = 0; + if (targetFrameIndex > lastFrameIndex) + framesInAdvance = targetFrameIndex - lastFrameIndex; + + if (targetFrameIndex < lastFrameIndex) + framesInAdvance = (bufferedReader.totalFrames - lastFrameIndex) + targetFrameIndex; + + frameDropped = framesInAdvance > 1 ? true : false; + if (frameDropped) + framesDroppedCounter += framesInAdvance - 1; + + //Debug.Log("Elapsed MS in sequence: " + elapsedMsSinceSequenceStart + ", target Index: " + targetFrameIndex + ", last frame: " + lastFrameIndex + ", target in advance: " + targetFrameIndex); + + bufferedReader.BufferFrames(targetFrameIndex, lastFrameIndex); + + if (framesInAdvance > 0) + { + //Check if our desired frame is inside the frame buffer and loaded, so that we can use it + int newBufferIndex = bufferedReader.GetBufferIndexForLoadedPlaybackIndex(targetFrameIndex); + + //Is the frame inside the buffer and fully loaded? + if (newBufferIndex > -1) + { + //Now that we a show a new frame, we mark the old played frame as consumed, and the new frame as playing + if (lastFrameBufferIndex > -1) + bufferedReader.frameBuffer[lastFrameBufferIndex].bufferState = BufferState.Consumed; + + bufferedReader.frameBuffer[newBufferIndex].bufferState = BufferState.Playing; + ShowFrame(bufferedReader.frameBuffer[newBufferIndex]); + lastFrameBufferIndex = newBufferIndex; + lastFrameIndex = targetFrameIndex; + + //Sometimes, the system might struggle to render a frame, or the application has a low framerate in general + //For performance tracking, we need to decouple the application framerate from our stream framerate. + //If we are lagging behind due to render reasons, but have sucessfully buffered up to the current target frame + //we still hit our target time window and the stream is performing well + //Therefore we substract the dropped frames from our deltatime + + if (frameDropped && framesInAdvance > 1) + sequenceDeltaTime -= (framesInAdvance - 1) * targetFrameTimeMs; + + float decay = 0.9f; + smoothedFPS = decay * smoothedFPS + (1.0f - decay) * (1000f / sequenceDeltaTime); + + lastFrameTime = sequenceDeltaTime; + sequenceDeltaTime = 0; + } + } + + if (frameDebugger != null) + { + frameDebugger.UpdateFrameDebugger(this); + } + + //TODO: Buffering callback } - } - if (frameDebugger != null) - { - frameDebugger.UpdateFrameDebugger(this); - } - - //TODO: Buffering callback - } + public void SetFrameTime(float frameTimeMS) + { + elapsedMsSinceSequenceStart = frameTimeMS; + lastFrameIndex = (int)(frameTimeMS / targetFrameTimeMs); + UpdateFrame(); + } - public void SetFrameTime(float frameTimeMS) - { - elapsedMsSinceSequenceStart = frameTimeMS; - lastFrameIndex = (int)(frameTimeMS / targetFrameTimeMs); - UpdateFrame(); - } + public void SetPlaybackFPS(float rate) + { + targetFrameTimeMs = 1000f / (float)rate; + smoothedFPS = rate; + } - /// - /// Render the frame - /// - /// - public void ShowFrame(Frame frame) - { - if (bufferedReader.sequenceConfig.geometryType == SequenceConfiguration.GeometryType.Point) - pointcloudRenderer?.SetFrame(frame); - else - meshSequenceRenderer?.RenderFrame(frame); + /// + /// Render the frame + /// + /// + public void ShowFrame(Frame frame) + { + if (bufferedReader.sequenceConfig.geometryType == SequenceConfiguration.GeometryType.Point) + pointcloudRenderer?.SetFrame(frame); + else + meshSequenceRenderer?.RenderFrame(frame); - frame.finishedBufferingTime = 0; - } + frame.finishedBufferingTime = 0; + } - public IPointCloudRenderer SetupPointcloudRenderer(BufferedGeometryReader reader, PointcloudRenderPath renderPath) - { - IPointCloudRenderer pcRenderer; + public IPointCloudRenderer SetupPointcloudRenderer(BufferedGeometryReader reader, PointcloudRenderPath renderPath) + { + IPointCloudRenderer pcRenderer; #if !SHADERGRAPH_AVAILABLE - if (renderPath != PointcloudRenderPath.Legacy) - { - Debug.LogWarning("Shadergraph package not available, falling back to legacy pointcloud sequence rendering"); - renderPath = PointcloudRenderPath.Legacy; - } + if (renderPath != PointcloudRenderPath.Legacy) + { + Debug.LogWarning("Shadergraph package not available, falling back to legacy pointcloud sequence rendering"); + renderPath = PointcloudRenderPath.Legacy; + } #endif - switch (renderPath) - { - case PointcloudRenderPath.Shadergraph: - pcRenderer = gameObject.AddComponent(); - break; - case PointcloudRenderPath.Legacy: - pcRenderer = gameObject.AddComponent(); - break; - case PointcloudRenderPath.PolySpatial: - pcRenderer = gameObject.AddComponent(); - break; - default: - pcRenderer = gameObject.AddComponent(); - break; - } - - (pcRenderer as Component).hideFlags = HideFlags.DontSave; - pcRenderer.Setup(reader.sequenceConfig, this.transform, pointSize, pointEmission, customMaterial, instantiateMaterial); - - return pcRenderer; - } + switch (renderPath) + { + case PointcloudRenderPath.Shadergraph: + pcRenderer = gameObject.AddComponent(); + break; + case PointcloudRenderPath.Legacy: + pcRenderer = gameObject.AddComponent(); + break; + case PointcloudRenderPath.PolySpatial: + pcRenderer = gameObject.AddComponent(); + break; + default: + pcRenderer = gameObject.AddComponent(); + break; + } + + (pcRenderer as Component).hideFlags = HideFlags.DontSave; + pcRenderer.Setup(reader.sequenceConfig, this.transform, pointSize, pointEmission, customMaterial, instantiateMaterial); + + return pcRenderer; + } - public IMeshSequenceRenderer SetupMeshSequenceRenderer(BufferedGeometryReader reader, PointcloudRenderPath renderPath) - { - IMeshSequenceRenderer msRenderer; + public IMeshSequenceRenderer SetupMeshSequenceRenderer(BufferedGeometryReader reader, PointcloudRenderPath renderPath) + { + IMeshSequenceRenderer msRenderer; #if !SHADERGRAPH_AVAILABLE - if (renderPath != PointcloudRenderPath.Legacy) - { - Debug.LogWarning("Shadergraph package not available, falling back to legacy mesh sequence rendering"); - renderPath = PointcloudRenderPath.Legacy; - } + if (renderPath != PointcloudRenderPath.Legacy) + { + Debug.LogWarning("Shadergraph package not available, falling back to legacy mesh sequence rendering"); + renderPath = PointcloudRenderPath.Legacy; + } #endif - if (renderPath == PointcloudRenderPath.PolySpatial) - msRenderer = gameObject.AddComponent(); - else - msRenderer = gameObject.AddComponent(); + if (renderPath == PointcloudRenderPath.PolySpatial) + msRenderer = gameObject.AddComponent(); + else + msRenderer = gameObject.AddComponent(); - (msRenderer as Component).hideFlags = HideFlags.HideAndDontSave; - msRenderer.Setup(this.transform, reader.sequenceConfig); + (msRenderer as Component).hideFlags = HideFlags.HideAndDontSave; + msRenderer.Setup(this.transform, reader.sequenceConfig); - if (customMaterial != null) - msRenderer.ChangeMaterial(customMaterial, instantiateMaterial); + if (customMaterial != null) + msRenderer.ChangeMaterial(customMaterial, instantiateMaterial); - //If we have a single texture in the sequence, we read it immeiatly - if (reader.sequenceConfig.textureMode == SequenceConfiguration.TextureMode.Single) - { - reader.SetupFrameForReading(reader.frameBuffer[0], reader.sequenceConfig, 0); - reader.ScheduleTextureReadJob(reader.frameBuffer[0], reader.GetDeviceDependentTexturePath(0)); - reader.frameBuffer[0].textureJobHandle.Complete(); - msRenderer.ApplySingleTexture(reader.frameBuffer[0]); - } + //If we have a single texture in the sequence, we read it immeiatly + if (reader.sequenceConfig.textureMode == SequenceConfiguration.TextureMode.Single) + { + reader.SetupFrameForReading(reader.frameBuffer[0], reader.sequenceConfig, 0); + reader.ScheduleTextureReadJob(reader.frameBuffer[0], reader.GetDeviceDependentTexturePath(0)); + reader.frameBuffer[0].textureJobHandle.Complete(); + msRenderer.ApplySingleTexture(reader.frameBuffer[0]); + } - return msRenderer; - } + return msRenderer; + } - public void SetPointSize(float pointSize) - { - pointcloudRenderer?.SetPointSize(pointSize); - thumbnailPCRenderer?.SetPointSize(pointSize); - } + public void SetPointSize(float pointSize) + { + pointcloudRenderer?.SetPointSize(pointSize); + thumbnailPCRenderer?.SetPointSize(pointSize); + } - public void SetPointEmission(float pointEmission) - { - pointcloudRenderer?.SetPointEmission(pointEmission); - thumbnailPCRenderer?.SetPointEmission(pointEmission); - } + public void SetPointEmission(float pointEmission) + { + pointcloudRenderer?.SetPointEmission(pointEmission); + thumbnailPCRenderer?.SetPointEmission(pointEmission); + } - public void SetMaterial(Material mat) - { - SetMaterial(mat, instantiateMaterial); - } + public void SetMaterial(Material mat) + { + SetMaterial(mat, instantiateMaterial); + } - public void SetMaterial(Material mat, bool instantiate) - { - customMaterial = mat; - pointcloudRenderer?.SetPointcloudMaterial(mat, instantiate); - thumbnailPCRenderer?.SetPointcloudMaterial(mat, instantiate); + public void SetMaterial(Material mat, bool instantiate) + { + customMaterial = mat; + pointcloudRenderer?.SetPointcloudMaterial(mat, instantiate); + thumbnailPCRenderer?.SetPointcloudMaterial(mat, instantiate); - meshSequenceRenderer?.ChangeMaterial(mat, instantiate); - thumbnailMeshRenderer?.ChangeMaterial(mat, instantiate); - } + meshSequenceRenderer?.ChangeMaterial(mat, instantiate); + thumbnailMeshRenderer?.ChangeMaterial(mat, instantiate); + } - public void SetRenderingPath(PointcloudRenderPath renderPath) - { - pointRenderPath = renderPath; - } + public void SetRenderingPath(PointcloudRenderPath renderPath) + { + pointRenderPath = renderPath; + } - public void ShowSequence() - { - pointcloudRenderer?.Show(); - meshSequenceRenderer?.Show(); - } + public void ShowSequence() + { + pointcloudRenderer?.Show(); + meshSequenceRenderer?.Show(); + } - public void HideSequence() - { - pointcloudRenderer?.Hide(); - meshSequenceRenderer?.Hide(); - } + public void HideSequence() + { + pointcloudRenderer?.Hide(); + meshSequenceRenderer?.Hide(); + } - public void Dispose() - { - pointcloudRenderer?.Dispose(); - meshSequenceRenderer?.Dispose(); + public void Dispose() + { + pointcloudRenderer?.Dispose(); + meshSequenceRenderer?.Dispose(); - thumbnailPCRenderer?.Dispose(); - meshSequenceRenderer?.Dispose(); + thumbnailPCRenderer?.Dispose(); + meshSequenceRenderer?.Dispose(); - bufferedReader?.DisposeFrameBuffer(true); - readerInitialized = false; - } + bufferedReader?.DisposeFrameBuffer(true); + readerInitialized = false; + } - [ExecuteInEditMode] - void OnDestroy() - { + [ExecuteInEditMode] + void OnDestroy() + { #if UNITY_EDITOR if (!Application.isPlaying) { @@ -365,10 +370,10 @@ void OnDestroy() return; } #endif - Dispose(); - } + Dispose(); + } - #region Thumbnail + #region Thumbnail #if UNITY_EDITOR @@ -431,17 +436,17 @@ public void ClearEditorThumbnail() DestroyImmediate(thumbnailPCRenderer as UnityEngine.Object); } #endif -#endregion + #endregion - #region Debug - void AttachFrameDebugger() - { + #region Debug + void AttachFrameDebugger() + { #if UNITY_EDITOR GameObject debugGO = Resources.Load("GSFrameDebugger") as GameObject; frameDebugger = Instantiate(debugGO).GetComponent(); frameDebugger.GetCanvas().renderMode = RenderMode.ScreenSpaceOverlay; #endif + } + #endregion } - #endregion - } } diff --git a/Runtime/Scripts/Playback/GeometrySequencePlayer.cs b/Runtime/Scripts/Playback/GeometrySequencePlayer.cs index 78d7a8c..d602da4 100644 --- a/Runtime/Scripts/Playback/GeometrySequencePlayer.cs +++ b/Runtime/Scripts/Playback/GeometrySequencePlayer.cs @@ -6,501 +6,513 @@ namespace BuildingVolumes.Player { - public class GeometrySequencePlayer : MonoBehaviour - { - GeometrySequenceStream stream; - - [SerializeField] - string relativePath = ""; - [SerializeField] - GeometrySequenceStream.PathType pathRelation; + public class GeometrySequencePlayer : MonoBehaviour + { + GeometrySequenceStream stream; - [SerializeField] - bool playAtStart = true; - [SerializeField] - bool loopPlay = true; - [SerializeField] - float playbackFPS = 30; + [SerializeField] + string relativePath = ""; + [SerializeField] + GeometrySequenceStream.PathType pathRelation; - public UnityEvent playbackEvents = new UnityEvent(); + [SerializeField] + bool playAtStart = true; + [SerializeField] + bool loopPlay = true; + [SerializeField] + float playbackFPS = 30; - float playbackTimeMs = 0; - bool play = false; - bool bufferingSequence = true; + public UnityEvent playbackEvents = new UnityEvent(); - public enum GSPlayerEvents { Stopped, Started, Paused, SequenceChanged, BufferingStarted, BufferingCompleted, PlaybackStarted, PlaybackFinished, Looped, FrameDropped } + float playbackTimeMs = 0; + bool play = false; + bool bufferingSequence = true; - // Start is called before the first frame update - void Start() - { - SetupGeometryStream(); + public enum GSPlayerEvents { Stopped, Started, Paused, SequenceChanged, BufferingStarted, BufferingCompleted, PlaybackStarted, PlaybackFinished, Looped, FrameDropped } - if (playAtStart) - if (!OpenSequence(relativePath, pathRelation, playbackFPS, playAtStart)) - Debug.LogError("Could not open sequence! Please check the path and files!"); - } + // Start is called before the first frame update + void Start() + { + SetupGeometryStream(); + if (playAtStart) + if (!OpenSequence(relativePath, pathRelation, playbackFPS, playAtStart)) + Debug.LogError("Could not open sequence! Please check the path and files!"); + } - /// - /// Add a Geometry Sequence Stream if there is non already existing on this gameobject. - /// Automatically performed when adding this component in the editor - /// - public void SetupGeometryStream() - { - if (stream == null) - { - stream = gameObject.GetComponent(); - if (stream == null) - stream = gameObject.AddComponent(); - } - } - /// - /// The main update loop. Only executes in the runtime/playmode - /// - private void Update() - { - if (play) - { - playbackTimeMs += Time.deltaTime * 1000; - - if (GetCurrentTime() >= GetTotalTime()) + /// + /// Add a Geometry Sequence Stream if there is non already existing on this gameobject. + /// Automatically performed when adding this component in the editor + /// + public void SetupGeometryStream() { - if (!loopPlay) - { - Stop(); - playbackEvents.Invoke(this, GSPlayerEvents.PlaybackFinished); - } - - else - { - GoToTime(0); - playbackEvents.Invoke(this, GSPlayerEvents.Looped); - } + if (stream == null) + { + stream = gameObject.GetComponent(); + if (stream == null) + stream = gameObject.AddComponent(); + } } - stream.UpdateFrame(); + /// + /// The main update loop. Only executes in the runtime/playmode + /// + private void Update() + { + if (play) + { + playbackTimeMs += Time.deltaTime * 1000; + + if (GetCurrentTime() >= GetTotalTime()) + { + if (!loopPlay) + { + Stop(); + playbackEvents.Invoke(this, GSPlayerEvents.PlaybackFinished); + } + + else + { + GoToTime(0); + playbackEvents.Invoke(this, GSPlayerEvents.Looped); + } + } + + stream.UpdateFrame(); + + if (GetFrameDropped()) + playbackEvents.Invoke(this, GSPlayerEvents.FrameDropped); + } + } - if (GetFrameDropped()) - playbackEvents.Invoke(this, GSPlayerEvents.FrameDropped); - } - } + //+++++++++++++++++++++ PLAYBACK API ++++++++++++++++++++++++ + + /// + /// Load a .ply sequence (and optionally textures) from the given path, and starts playback if autoplay is enabled. + /// Returns false when sequence could not be loaded, see Unity Console output for details in this case. + /// + /// The path to the sequence + /// Which location is the path relative to? + /// Desired playback framerate (Default = 30 fps) + /// Start the playback as soon as possible. Might have a small delay as a frames need to load first + /// Preload the first few frames into memory before playing. Only applicable if autostart set to false. < + /// True when the sequence could sucessfully be loaded, false if an error has occured + public bool OpenSequence(string path, GeometrySequenceStream.PathType relativeTo, float playbackFPS = 30f, bool autoplay = false, bool buffer = true) + { + if (path.Length > 0) + { + this.playbackFPS = playbackFPS; + SetPath(path, relativeTo); + return LoadCurrentSequence(autoplay, buffer); + } + + return false; + } - //+++++++++++++++++++++ PLAYBACK API ++++++++++++++++++++++++ - - /// - /// Load a .ply sequence (and optionally textures) from the given path, and starts playback if autoplay is enabled. - /// Returns false when sequence could not be loaded, see Unity Console output for details in this case. - /// - /// The path to the sequence - /// Which location is the path relative to? - /// Desired playback framerate (Default = 30 fps) - /// Start the playback as soon as possible. Might have a small delay as a frames need to load first - /// Preload the first few frames into memory before playing. Only applicable if autostart set to false. < - /// True when the sequence could sucessfully be loaded, false if an error has occured - public bool OpenSequence(string path, GeometrySequenceStream.PathType relativeTo, float playbackFPS = 30f, bool autoplay = false, bool buffer = true) - { - if (path.Length > 0) - { - this.playbackFPS = playbackFPS; - SetPath(path, relativeTo); - return LoadCurrentSequence(autoplay, buffer); - } - - return false; - } + /// + /// (Re)loads the sequence which is currently set in the player, optionally starts playback. + /// + /// Start playback immediatly after loading + /// Preload the first few frames into memory before playing. Only applicable if autostart set to false. < + /// True when the sequence could sucessfully be reloaded, false if an error has occured + public bool LoadCurrentSequence(bool autoplay = false, bool buffer = true) + { + if (GetRelativeSequencePath().Length == 0) + return false; - /// - /// (Re)loads the sequence which is currently set in the player, optionally starts playback. - /// - /// Start playback immediatly after loading - /// Preload the first few frames into memory before playing. Only applicable if autostart set to false. < - /// True when the sequence could sucessfully be reloaded, false if an error has occured - public bool LoadCurrentSequence(bool autoplay = false, bool buffer = true) - { - if (GetRelativeSequencePath().Length == 0) - return false; + playbackEvents.Invoke(this, GSPlayerEvents.SequenceChanged); - playbackEvents.Invoke(this, GSPlayerEvents.SequenceChanged); + bool sucess = stream.ChangeSequence(GetAbsoluteSequencePath(), playbackFPS); - bool sucess = stream.ChangeSequence(GetAbsoluteSequencePath(), playbackFPS); + if (sucess) + { + if (buffer || autoplay) + { + playbackEvents.Invoke(this, GSPlayerEvents.BufferingStarted); + stream.bufferedReader.BufferFrames(0, 0); + StartCoroutine(WaitForBufferingCompleted(autoplay)); + } + } - if (sucess) - { - if (buffer || autoplay) - { - playbackEvents.Invoke(this, GSPlayerEvents.BufferingStarted); - stream.bufferedReader.BufferFrames(0, 0); - StartCoroutine(WaitForBufferingCompleted(autoplay)); + return sucess; } - } - - return sucess; - } - - - /// - /// Set a new path in the player, but don't load the sequence. Use LoadCurrentSequence() to actually load it, or OpenSequence() to set and load a sequence. - /// - /// The relative or absolute path to the new Sequence - /// Specifiy to which path your sequence path is relative, or if it is an absolute path - public void SetPath(string path, GeometrySequenceStream.PathType relativeTo) - { - if (path.Length < 1) - return; - this.relativePath = path; - pathRelation = relativeTo; - return; - } - /// - /// Terminates playback of the current sequence and removes it from the player - /// - public void ClearSequence() - { - stream.Dispose(); - } + /// + /// Set a new path in the player, but don't load the sequence. Use LoadCurrentSequence() to actually load it, or OpenSequence() to set and load a sequence. + /// + /// The relative or absolute path to the new Sequence + /// Specifiy to which path your sequence path is relative, or if it is an absolute path + public void SetPath(string path, GeometrySequenceStream.PathType relativeTo) + { + if (path.Length < 1) + return; - /// - /// Start Playback from the current location. - /// - public void Play() - { - play = true; - playbackEvents.Invoke(this, GSPlayerEvents.Started); - } + this.relativePath = path; + pathRelation = relativeTo; + return; + } - /// - /// Pause current playback - /// - public void Pause() - { - play = false; - playbackEvents.Invoke(this, GSPlayerEvents.Paused); - } + /// + /// Terminates playback of the current sequence and removes it from the player + /// + public void ClearSequence() + { + stream.Dispose(); + } - /// - /// Stops the playback - /// - public void Stop() - { - Pause(); - GoToFrame(0); - playbackEvents.Invoke(this, GSPlayerEvents.Stopped); - } + /// + /// Start Playback from the current location. + /// + public void Play() + { + play = true; + playbackEvents.Invoke(this, GSPlayerEvents.Started); + } - /// - /// Activate or deactivate looped playback - /// - /// - public void SetLoopPlay(bool enabled) - { - loopPlay = enabled; - } + /// + /// Pause current playback + /// + public void Pause() + { + play = false; + playbackEvents.Invoke(this, GSPlayerEvents.Paused); + } - /// - /// Activate or deactivate automatic playback (when the scene starts) - /// - /// - public void SetAutoStart(bool enabled) - { - playAtStart = false; - } + /// + /// Stops the playback + /// + public void Stop() + { + Pause(); + GoToFrame(0); + playbackEvents.Invoke(this, GSPlayerEvents.Stopped); + } - /// - /// Seeks to the start of the sequence and then starts playback - /// - public bool PlayFromStart() - { - if (GoToFrame(0)) - { - Play(); - return true; - } + /// + /// Activate or deactivate looped playback + /// + /// + public void SetLoopPlay(bool enabled) + { + loopPlay = enabled; + } - return false; - } + /// + /// Activate or deactivate automatic playback (when the scene starts) + /// + /// + public void SetAutoStart(bool enabled) + { + playAtStart = false; + } - /// - /// Goes to a specific frame. Use GetTotalFrames() to check how many frames the clip contains - /// - /// - public bool GoToFrame(int frame) - { - if (stream != null) - { - float time = (frame * stream.targetFrameTimeMs) / 1000; - return GoToTime(time); - } + /// + /// Seeks to the start of the sequence and then starts playback + /// + public bool PlayFromStart() + { + if (GoToFrame(0)) + { + Play(); + return true; + } - return false; - } + return false; + } - /// - /// Goes to a specific time in a clip. The time is dependent on the framerate e.g. the same clip at 30 FPS is twice as long as at 60 FPS. - /// Use GetTotalTime() to see how long the clip is - /// - /// - /// - public bool GoToTime(float timeInSeconds) - { - if (timeInSeconds < 0 || timeInSeconds > GetTotalTime()) - return false; + /// + /// Goes to a specific frame. Use GetTotalFrames() to check how many frames the clip contains + /// + /// + public bool GoToFrame(int frame) + { + if (stream != null) + { + float time = (frame * stream.targetFrameTimeMs) / 1000; + return GoToTime(time); + } - playbackTimeMs = timeInSeconds * 1000; - stream.SetFrameTime(playbackTimeMs); - return true; + return false; + } - } + /// + /// Goes to a specific time in a clip. The time is dependent on the framerate e.g. the same clip at 30 FPS is twice as long as at 60 FPS. + /// Use GetTotalTime() to see how long the clip is + /// + /// + /// + public bool GoToTime(float timeInSeconds) + { + if (timeInSeconds < 0 || timeInSeconds > GetTotalTime()) + return false; - /// - /// Makes the sequence visible - /// - public void Show() - { - stream.ShowSequence(); - } + playbackTimeMs = timeInSeconds * 1000; + stream.SetFrameTime(playbackTimeMs); + return true; - /// - /// Hides the sequence, but still lets it run in the background - /// - public void Hide() - { - stream.HideSequence(); - } + } + /// + /// Sets a new framerate. + /// This will instantly change the framerate to the desired one. + /// Use to fasten or slow the playback during runtime. + /// For initialisation use + /// + /// new framerate + public void SetFrameRate(float rate) + { + this.playbackFPS = rate; + stream.SetPlaybackFPS(rate); + } - /// - /// Set the render path for pointclouds. Render path will only change once the - /// sequence is also being changed or loaded - /// - /// - public void SetRenderPath(GeometrySequenceStream.PointcloudRenderPath renderPath) - { - stream.pointRenderPath = renderPath; - } + /// + /// Makes the sequence visible + /// + public void Show() + { + stream.ShowSequence(); + } - /// - /// Gets the absolute path to the folder containing the sequence - /// - /// - public string GetAbsoluteSequencePath() - { - string absolutePath = ""; + /// + /// Hides the sequence, but still lets it run in the background + /// + public void Hide() + { + stream.HideSequence(); + } - //Set the correct absolute path depending on the files location - if (pathRelation == GeometrySequenceStream.PathType.RelativeToDataPath) - absolutePath = Path.Combine(UnityEngine.Application.dataPath, this.relativePath); + /// + /// Set the render path for pointclouds. Render path will only change once the + /// sequence is also being changed or loaded + /// + /// + public void SetRenderPath(GeometrySequenceStream.PointcloudRenderPath renderPath) + { + stream.pointRenderPath = renderPath; + } - if (pathRelation == GeometrySequenceStream.PathType.RelativeToStreamingAssets) - absolutePath = Path.Combine(UnityEngine.Application.streamingAssetsPath, this.relativePath); + /// + /// Gets the absolute path to the folder containing the sequence + /// + /// + public string GetAbsoluteSequencePath() + { + string absolutePath = ""; - if (pathRelation == GeometrySequenceStream.PathType.RelativeToPersistentDataPath) - absolutePath = Path.Combine(UnityEngine.Application.persistentDataPath, this.relativePath); + //Set the correct absolute path depending on the files location + if (pathRelation == GeometrySequenceStream.PathType.RelativeToDataPath) + absolutePath = Path.Combine(UnityEngine.Application.dataPath, this.relativePath); - if (pathRelation == GeometrySequenceStream.PathType.AbsolutePath) - absolutePath = this.relativePath; + if (pathRelation == GeometrySequenceStream.PathType.RelativeToStreamingAssets) + absolutePath = Path.Combine(UnityEngine.Application.streamingAssetsPath, this.relativePath); + if (pathRelation == GeometrySequenceStream.PathType.RelativeToPersistentDataPath) + absolutePath = Path.Combine(UnityEngine.Application.persistentDataPath, this.relativePath); - return absolutePath; - } + if (pathRelation == GeometrySequenceStream.PathType.AbsolutePath) + absolutePath = this.relativePath; - /// - /// Gets the relative path to the sequence directory. Get the path which it is relative to with GetRelativeTo() - /// - /// - public string GetRelativeSequencePath() - { - return relativePath; - } - /// - /// Get the location to which the relativePath is relative to. - /// - /// The relative location. - public GeometrySequenceStream.PathType GetRelativeTo() - { - return pathRelation; - } + return absolutePath; + } - /// - /// Is a sequence currently loaded and ready to play? - /// - /// True if a sequence is initialized and ready to play, false if not - public bool IsInitialized() - { - return stream.readerInitialized; - } + /// + /// Gets the relative path to the sequence directory. Get the path which it is relative to with GetRelativeTo() + /// + /// + public string GetRelativeSequencePath() + { + return relativePath; + } + /// + /// Get the location to which the relativePath is relative to. + /// + /// The relative location. + public GeometrySequenceStream.PathType GetRelativeTo() + { + return pathRelation; + } + /// + /// Is a sequence currently loaded and ready to play? + /// + /// True if a sequence is initialized and ready to play, false if not + public bool IsInitialized() + { + return stream.readerInitialized; + } - /// - /// Is the current clip playing? - /// - /// - public bool IsPlaying() - { - return play; - } - /// - /// Is looped playback enabled? - /// - /// - public bool GetLoopingEnabled() - { - return loopPlay; - } - /// - /// At which frame is the playback currently? - /// - /// - public int GetCurrentFrameIndex() - { - if (stream != null) - return stream.lastFrameIndex; - return -1; - } + /// + /// Is the current clip playing? + /// + /// + public bool IsPlaying() + { + return play; + } - /// - /// At which time is the playback currently in seconds? - /// Note that the time is dependent on the framerate e.g. the same clip at 30 FPS is twice as long as at 60 FPS. - /// - /// - public float GetCurrentTime() - { - return playbackTimeMs / 1000; - } + /// + /// Is looped playback enabled? + /// + /// + public bool GetLoopingEnabled() + { + return loopPlay; + } - /// - /// How many frames are there in total in the whole sequence? - /// - /// - public int GetTotalFrames() - { - if (stream != null) - if (stream.bufferedReader != null) - return stream.bufferedReader.totalFrames; - return -1; - } + /// + /// At which frame is the playback currently? + /// + /// + public int GetCurrentFrameIndex() + { + if (stream != null) + return stream.lastFrameIndex; + return -1; + } - /// - /// How long is the sequence in total? - /// Note that the time is dependent on the framerate e.g. the same clip at 30 FPS is twice as long as at 60 FPS. - /// - /// - public float GetTotalTime() - { - return GetTotalFrames() / GetTargetFPS(); - } + /// + /// At which time is the playback currently in seconds? + /// Note that the time is dependent on the framerate e.g. the same clip at 30 FPS is twice as long as at 60 FPS. + /// + /// + public float GetCurrentTime() + { + return playbackTimeMs / 1000; + } - /// - /// The target fps is the framerate we _want_ to achieve in playback. However, this is not guranteed, if system resources - /// are too low. Use GetActualFPS() to see if you actually achieve this framerate - /// - /// - public float GetTargetFPS() - { - if (stream != null) - return 1000 / stream.targetFrameTimeMs; - return -1; - } + /// + /// How many frames are there in total in the whole sequence? + /// + /// + public int GetTotalFrames() + { + if (stream != null) + if (stream.bufferedReader != null) + return stream.bufferedReader.totalFrames; + return -1; + } - /// - /// What is the actual current playback framerate? If the framerate is much lower than the target framerate, - /// consider reducing the complexity of your sequence, and don't forget to disable any V-Sync (VSync, FreeSync, GSync) methods! - /// - /// - public float GetActualFPS() - { - if (stream != null) - return stream.smoothedFPS; - return -1; - } + /// + /// How long is the sequence in total? + /// Note that the time is dependent on the framerate e.g. the same clip at 30 FPS is twice as long as at 60 FPS. + /// + /// + public float GetTotalTime() + { + return GetTotalFrames() / GetTargetFPS(); + } - /// - /// Check if there have been framedrops since you last checked this function - /// Too many framedrops mean the system can't keep up with the playback, - /// and you should reduce your Geometric complexity or framerate - /// - /// - public bool GetFrameDropped() - { - if (stream != null) - { - bool dropped = stream.frameDropped; - stream.frameDropped = false; - return dropped; - } - - return false; - } + /// + /// The target fps is the framerate we _want_ to achieve in playback. However, this is not guranteed, if system resources + /// are too low. Use GetActualFPS() to see if you actually achieve this framerate + /// + /// + public float GetTargetFPS() + { + if (stream != null) + return 1000 / stream.targetFrameTimeMs; + return -1; + } - /// - /// Checks if the playback cache has been filled and is ready to play. - /// If it is, playback starts immediatly once you call Play() - /// - /// - public bool IsCacheFilled() - { - if (stream != null) - { - if (stream.bufferedReader != null) + /// + /// What is the actual current playback framerate? If the framerate is much lower than the target framerate, + /// consider reducing the complexity of your sequence, and don't forget to disable any V-Sync (VSync, FreeSync, GSync) methods! + /// + /// + public float GetActualFPS() { - bool cacheReady = true; + if (stream != null) + return stream.smoothedFPS; + return -1; + } - for (int i = 0; i < stream.bufferedReader.frameBuffer.Length; i++) - { - if (!stream.bufferedReader.IsFrameBuffered(stream.bufferedReader.frameBuffer[i])) - cacheReady = false; - } + /// + /// Check if there have been framedrops since you last checked this function + /// Too many framedrops mean the system can't keep up with the playback, + /// and you should reduce your Geometric complexity or framerate + /// + /// + public bool GetFrameDropped() + { + if (stream != null) + { + bool dropped = stream.frameDropped; + stream.frameDropped = false; + return dropped; + } + + return false; + } - return cacheReady; + /// + /// Checks if the playback cache has been filled and is ready to play. + /// If it is, playback starts immediatly once you call Play() + /// + /// + public bool IsCacheFilled() + { + if (stream != null) + { + if (stream.bufferedReader != null) + { + bool cacheReady = true; + + for (int i = 0; i < stream.bufferedReader.frameBuffer.Length; i++) + { + if (!stream.bufferedReader.IsFrameBuffered(stream.bufferedReader.frameBuffer[i])) + cacheReady = false; + } + + return cacheReady; + } + } + + return false; } - } - return false; - } + /// + /// Used to wait until buffering has been completed before starting the sequence + /// + /// + /// + + private IEnumerator WaitForBufferingCompleted(bool playFromStart) + { + if (bufferingSequence) + { + while (!IsCacheFilled()) + yield return null; - /// - /// Used to wait until buffering has been completed before starting the sequence - /// - /// - /// + bufferingSequence = false; - private IEnumerator WaitForBufferingCompleted(bool playFromStart) - { - if (bufferingSequence) - { - while (!IsCacheFilled()) - yield return null; + playbackEvents.Invoke(this, GSPlayerEvents.BufferingCompleted); - bufferingSequence = false; + if (playFromStart) + { + PlayFromStart(); + playbackEvents.Invoke(this, GSPlayerEvents.PlaybackStarted); + } + } - playbackEvents.Invoke(this, GSPlayerEvents.BufferingCompleted); + } - if (playFromStart) + /// + /// Get the currently set render path. This might not be the actual render path being + /// used, but could also be the next scheduled render path + /// + /// + public GeometrySequenceStream.PointcloudRenderPath GetRenderingPath() { - PlayFromStart(); - playbackEvents.Invoke(this, GSPlayerEvents.PlaybackStarted); + return stream.pointRenderPath; } - } - } - /// - /// Get the currently set render path. This might not be the actual render path being - /// used, but could also be the next scheduled render path - /// - /// - public GeometrySequenceStream.PointcloudRenderPath GetRenderingPath() - { - return stream.pointRenderPath; - } - - - #region Thumbnail + #region Thumbnail #if UNITY_EDITOR //Functions used to show the thumbnail in the Editor @@ -523,19 +535,19 @@ public void ClearThumbnail() stream.ClearEditorThumbnail(); } #endif - #endregion + #endregion - #region Obsolete + #region Obsolete - [Obsolete("LoadSequence is deprecated, please use OpenSequence instead.", true)] - public void LoadSequence() - { } + [Obsolete("LoadSequence is deprecated, please use OpenSequence instead.", true)] + public void LoadSequence() + { } - [Obsolete("ReloadSequence is deprecated, please use LoadCurrentSequence instead.", true)] - public void ReloadSequence() - { } + [Obsolete("ReloadSequence is deprecated, please use LoadCurrentSequence instead.", true)] + public void ReloadSequence() + { } - #endregion - } + #endregion + } }