diff --git a/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/CloudController/PointCloudLoader.cs b/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/CloudController/PointCloudLoader.cs index dc8c882..69d4cae 100644 --- a/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/CloudController/PointCloudLoader.cs +++ b/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/CloudController/PointCloudLoader.cs @@ -48,10 +48,6 @@ void Start() { private void LoadHierarchy() { try { - if (!cloudPath.EndsWith("/")) { - cloudPath = cloudPath + "/"; - } - PointCloudMetaData metaData = CloudLoader.LoadMetaData(cloudPath, false); setController.UpdateBoundingBox(this, metaData.boundingBox_transformed, metaData.tightBoundingBox_transformed); diff --git a/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/Loading/CloudLoader.cs b/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/Loading/CloudLoader.cs index 459a02a..503e030 100644 --- a/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/Loading/CloudLoader.cs +++ b/PointCloudRenderer/Assets/Scripts/BAPointCloudRenderer/Loading/CloudLoader.cs @@ -4,14 +4,21 @@ using System.Text; using System.Net; using System; +using System.Diagnostics; using UnityEngine; using System.Linq; +using Debug = UnityEngine.Debug; namespace BAPointCloudRenderer.Loading { /// /// Provides methods for loading point clouds from the file system /// class CloudLoader { + public static bool isCloudOnline; + + public enum FileTypeV2 { + HIERARCHY, OCTREE + } /* Loads the metadata from the json-file in the given cloudpath */ /// @@ -19,45 +26,37 @@ class CloudLoader { /// /// Folderpath of the cloud or URL to download the cloud from. In the latter case, it will be downloaded to a /temp folder /// True, if the center of the cloud should be moved to the origin - public static PointCloudMetaData LoadMetaData(string cloudPath, bool moveToOrigin = false) { + public static PointCloudMetaData LoadMetaData(string fullPath, bool moveToOrigin = false) { string jsonfile = ""; - //Debug.Log(cloudPath); - bool isCloudOnline = Uri.IsWellFormedUriString(cloudPath, UriKind.Absolute); - if (isCloudOnline){ + isCloudOnline = Uri.IsWellFormedUriString(fullPath, UriKind.Absolute); + if (isCloudOnline) { WebClient client = new WebClient(); - Stream stream = client.OpenRead(cloudPath + "cloud.js"); + Stream stream = client.OpenRead(fullPath); StreamReader reader = new StreamReader(stream); jsonfile = reader.ReadToEnd(); reader.Close(); }else{ - string filePath; - if (File.Exists(cloudPath + "cloud.js")) - { - filePath = cloudPath + "cloud.js"; - } - else if (File.Exists(cloudPath + "metadata.json")) - { - filePath = cloudPath + "metadata.json"; - } - else - { - Debug.LogError("Unable to find neither cloud.js nor metadata.json from " + cloudPath); - throw new Exception("Unable to find neither cloud.js nor metadata.json from " + cloudPath); + if (!File.Exists (fullPath)) { + Debug.LogError("Unable to find file from " + fullPath); + throw new Exception("Unable to find file from " + fullPath); } - if (filePath.Length > 0) - { - using (StreamReader reader = new StreamReader(filePath, Encoding.Default)) - { - jsonfile = reader.ReadToEnd(); - reader.Close(); - } + if (fullPath.Length > 0) { + using StreamReader reader = new StreamReader(fullPath, Encoding.Default); + jsonfile = reader.ReadToEnd(); + reader.Close(); } } + + int lastSlashIndex = fullPath.LastIndexOf('/'); + string cloudPath = fullPath.Substring (0, lastSlashIndex + 1); + if (cloudPath == null) { + Debug.LogError("Unable to find directory from " + fullPath); + throw new Exception("Unable to find file directory " + fullPath); + } PointCloudMetaData metaData = PointCloudMetaDataReader.ReadFromJson(jsonfile, moveToOrigin); metaData.cloudName = cloudPath.Substring(0, cloudPath.Length-1).Substring(cloudPath.Substring(0, cloudPath.Length - 1).LastIndexOf("/") + 1); - //Debug.Log(metaData.cloudName); if (isCloudOnline){ metaData.cloudUrl = cloudPath; @@ -116,12 +115,13 @@ public static void LoadPointsForNode(Node node) { /// /// /// - private static void LoadHierarchy(PointCloudMetaData metaData, ref Node node) + /// + private static void LoadHierarchy (PointCloudMetaData metaData, ref Node node) { // sanitycheck. if (node.hierarchyByteSize > 0) { - byte[] data = ReadFromFile(metaData.cloudPath + "hierarchy.bin", (long)node.hierarchyByteOffset, node.hierarchyByteSize); + byte[] data = ReadFromFile(isCloudOnline ? metaData.cloudUrl : metaData.cloudPath, (long)node.hierarchyByteOffset, node.hierarchyByteSize, FileTypeV2.HIERARCHY, isCloudOnline); if (data.Length == (int)node.hierarchyByteSize) { ParseHierarchy(ref node, data); @@ -306,18 +306,21 @@ private static BoundingBox CalculateBoundingBox(BoundingBox parent, int index) { /// /// /// - private static void LoadPoints(string dataRPath, PointCloudMetaData metaData, Node node) { + /// + private static void LoadPoints (string dataRPath, PointCloudMetaData metaData, Node node) { // in potree v2 type 2 nodes are proxies and their hierarchy // yearns to be loaded just-in-time. if (metaData.version == "2.0" && node.type == 2) { LoadHierarchy(metaData, ref node); } - byte[] data = metaData.version switch - { - "2.0" => ReadFromFile(metaData.cloudPath + "octree.bin", (long)node.byteOffset, node.byteSize), - _ => FindAndLoadFile(dataRPath, metaData, node.Name, ".bin"), - }; + byte[] data = null; + + if (metaData.version.Equals ("2.0")) { + data = ReadFromFile (isCloudOnline ? metaData.cloudUrl : metaData.cloudPath, (long) node.byteOffset, node.byteSize, FileTypeV2.OCTREE, isCloudOnline); + } else { + data = FindAndLoadFile (dataRPath, metaData, node.Name, ".bin"); + } int pointByteSize = metaData.pointByteSize; int numPoints = data.Length / pointByteSize; int offset = 0, toSetOff = 0; @@ -429,27 +432,63 @@ private static byte[] FindAndLoadFile(string dataRPath, PointCloudMetaData metaD } return null; } + /// /// used only for Potree v2. for now. /// /// /// /// + /// + /// /// - private static byte[] ReadFromFile(string fileNameWithPath, long offset, UInt64 size) + private static byte[] ReadFromFile (string fileNameWithPath, long offset, ulong size, FileTypeV2 fileType, bool isURL = false) { + switch (fileType) { + case FileTypeV2.HIERARCHY : + fileNameWithPath = fileNameWithPath + "hierarchy.bin"; + break; + case FileTypeV2.OCTREE : + fileNameWithPath = fileNameWithPath + "octree.bin"; + break; + } + if (size == 0) { return new byte[] { }; } byte[] returnable = new byte[size]; - - if (File.Exists(fileNameWithPath)) - { - using FileStream stream = File.OpenRead(fileNameWithPath); - stream.Seek(offset, SeekOrigin.Begin); - stream.Read(returnable, 0, (int)size); - stream.Close(); + + if (!isURL) { + if (File.Exists(fileNameWithPath)) + { + using FileStream stream = File.OpenRead(fileNameWithPath); + stream.Seek(offset, SeekOrigin.Begin); + stream.Read(returnable, 0, (int)size); + stream.Close(); + } + } else { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fileNameWithPath); + request.AddRange(offset, offset + (long)size - 1); + + try { + using HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + using Stream stream = response.GetResponseStream(); + if (stream == null) + return null; + + int bytesRead = 0, totalBytesRead = 0; + + while ((bytesRead = stream.Read(returnable, totalBytesRead, (int)size - totalBytesRead)) > 0) + { + totalBytesRead += bytesRead; + } + } + catch (WebException ex) + { + Debug.Log($"Error downloading data: {ex.Message}"); + return null; + } } return returnable; diff --git a/README.md b/README.md index f7e076d..af3fa46 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ PointCloud-BachelorThesis Project files for my bachelor thesis on rendering large point clouds in Unity. + +## Range request + +We have to do range request to get the part of the remote file we want, so we're using range request as Potree is doing. + ## Ressources Please refer to the code documentation for details about the classes and scripts (Folder "/doc"). For details about the algorithms please refer to the bachelor thesis (https://www.cg.tuwien.ac.at/research/publications/2017/FRAISS-2017-PCU/). @@ -10,6 +15,26 @@ Below you will find a Getting-Started-Guide Download the current version: https://github.com/SFraissTU/BA_PointCloud/releases/ +### Version PullRequest PotreeRemoveV2 (13/08/2024) + +#### Remote URL + +We would like to use the PotreeV2 format for remote url, mainly because we might have to handle several Go of data. + +#### Remote/local URL handling with v1/v2 version + +For this we had to transform the URL given to the Cloud loader : + +Potree1 : +Clouds/Lion -> Clouds/Lion/cloud.js +https://remoteurl.com/Lion -> https://remoteurl.com/Lion/cloud.js + +Potree2 : +Clouds/Lion2 -> Clouds/Lion2/metadata.json +https://remoteurl.com/Lion2 -> https://remoteurl.com/Lion2/metadata.json + +We did this so codebase can handle in a easier way the remote/local and v1/v2 cases. + ### Version 1.6 (09.07.2023) Changes: * Support for Potree Format V2 (Provided by [cognitivedata](https://github.com/cognitivedata))